felix-cao / Blog

A little progress a day makes you a big success!
31 stars 4 forks source link

JavaScript 正则表达式 #123

Open felix-cao opened 5 years ago

felix-cao commented 5 years ago

正则表达式是一种查找以及字符串替换操作。简单点说就是按照某种规则去匹配符合条件的字符串,正则表达式就是这个规则。

正则表达式是匹配模式,要么匹配字符,要么匹配位置。请记住这句话。

有两个便捷的正则表达式工具: regexperregex101

一、几个概念

1.1、定义

JavaScript 通过内置对象 RegExp 支持正则表达式,提供了两种方式来定义正则表达式规则:

1.2、 模式修饰符

1.4、两种模糊匹配

1.4.1、横向模糊匹配

横向模糊指的是,一个正则可匹配的字符串的长度不是固定的,可以是多种情况的。其实现的方式是使用量词。譬如 {m,n},表示连续出现最少 m 次,最多 n 次。 比如 /ab{2,5}c/ 表示匹配这样一个字符串:第一个字符是 a,接下来是2到5个字符 b,最后是字符 c。在 regexper 上跑一把看看

1.4.2、纵向模糊匹配

纵向模糊指的是,一个正则匹配的字符串,具体到某一位字符时,它可以不是某个确定的字符,可以有多种可能。其实现的方式是使用字符类。譬如 [abc],表示该字符是可以字符 abc 中的任何一个。比如 /a[123]b/ 可以匹配如下三种字符串:a1ba2ba3b。在 regexper 上跑一把看看

二、字符类和范围类

2.1、 字符类

[] 来构建一个简单的类。 字符类具有“或”的含义,所谓类是指符合某些特征的对象,一个泛指而不是一个特指。

比如:表达式 c[abc]t , 把字母 abc 归为一类,表达式可以匹配这类中的一个字符。

再比如:表达式 c[abc]t , 表示可以匹配的字符串是以 c 开头,接着是 abc 中的任何一个字符,最后以 t 结尾“。在文本的实际应用中,这样的正则表达式可以匹配:cat, cbt, cct 三种字符串。

在字符类中,字符的重复和出现顺序并不重要,如 [cbbcaa][abc] 是相同的。

2.2、字符类取反, ^(脱字符)

例如 [^abc],表示是一个除 abc 之外的任意一个字符。字符组的第一位放 ^(脱字符),表示求反的概念。

2.3、范围类

- 单个中横杠在 [] 内部表示范围的意思。如果要匹配 - ,可以直接加载后面。

比如:使用字符类匹配数字则为 [0123456789] 这样比较麻烦,使用 [0-9] 简写,表示任意数字.

'2016-11-27'.replace(/[0-9]/g, 'c'); // cccc-cc-cc
'2016-11-27'.replace(/[0-9-]/g, 'c'); // cccccccccc

2.4、预定义类

预定义字符类简称预定义类,正则表达式提供预定义类来匹配常见的字符类,有了这些预定义类,我们写一些正则就简便多了。

字符 等价意 含义
. [^\r\n] 除了回车换行之外的所有字符
\d [0-9] 数字字符 digital
\D [^0-9] 非数字字符 digital
\s [\t\n\x0B\f\r] 空白字符 space
\S [^\t\n\x0B\f\r] 非空白字符 space
\w [a-zA-Z0-9_] 字母数字下划线 word
\W [^a-zA-Z0-9_] 非字母数字下划线 word

匹配任意字符:[\d\D], [\w\W], [\s\S], [^] 这四个中的任意个都可以

三、量词

量词也称重复,在字符或字符集之后,使用 {} 大括号标识重复, 格式为:{m,n}

3.1、量词的简写

量词 含义 记忆方法
{m,} 至少出现m次
{m} 出现m次
{n,m} 最少n次,最多m次
? 表示 {0,1},比如 colou?r 表示匹配 colourcolor 有吗?
* {0,},匹配任意次 看天上的星星,可能一颗也没有,可能很多
+ {1, }匹配一个或一个以上 加号是追加的意思,得先有一个才能追加

3.2、贪婪匹配和非贪婪匹配

提供一个惰性匹配的记忆方式是:量词后面加个问号,问一问你知足了吗,你很贪婪吗?不贪婪,最低要求就够了! 我们在 regex101 试试下面两行代码,然后去体会一下匹配结果。

var regex = /\d{2,5}/g; // 再试试加 ? 后 var regex = /\d{2,5}?/g; 
var string = "123456";

四、边界字符与位置匹配

常见的边界字符

字符 含义
^ 以 xxx 开始,文本分界,在字符类中表示字符类取反
$ 以 xxx 结束,文本分界
\b 单词边界或单词分隔符
\B 非单词边界
?= 正向先行断言 positive lookahead Assertions
?! 负向先行断言 negative lookahead Assertions

比如我们把字符串的开头和结尾用"#"替换(位置可以替换成字符的!):

var result = "Felix".replace(/^|$/g, '#');
console.log(result); 
// => "#Felix#"

多行匹配模式时,二者是行的概念,这个需要我们的注意:

var result = "I\nlove\nFelix".replace(/^|$/gm, '#');
console.log(result);
/*
#I#
#love#
#Felix#
*/

对于位置的理解,我们可以理解成空字符"",比如"hello"字符串等价于如下的形式:

"hello" == "" + "h" + "" + "e" + "" + "l" + "" + "l" + "o" + "";

数字的千分位表示法

var res = '12345678'.replace(/(?=(\d{3})+$)/g, ',')
console.log(res); // "12,345,678"
var res = '123456789'.replace(/(?!^)(?=(\d{3})+$)/g, ',')
console.log(res); // "123,456,789"

五、分支与分组

5.1、分支

多选分支,也叫选择匹配,使用 | 来分割可以匹配的不同选择

5.2、分组

使用括号 () 表示分组来匹配一组符号,

我们知道 /a+/ 匹配连续出现的 a,而要匹配连续出现的 ab 时,需要使用 /(ab)+/。其中圆括号是提供分组功能,使量词 + 作用于 ab 这个整体,

六、捕获

捕获是 () 的一个非常重要用途,有了它,我们就可以进行数据提取,以及更强大的替换操作。

(\w*)ility, 表示匹配以 ility 结尾的词。第一个被捕获的部分是由 \w* 控制的,比如输入的文本内容中有单词 accessibility,那么首先被捕获的部分是 accessib,如果输入的文本中有单独的 ility, 则首先被捕获的是一个空字符串。

捕获组从左向右编号,也就是只需要对左括号计数。如(\w+) had a ((\w+) \w+), 输入的内容是 I had a nice day

在其他的一些正则表达式的 API 实现中,可能是从铺获组1开始编号的。

6.1、提取数据

如提取出年、月、日,可以这么做:

var regex = /(\d{4})-(\d{2})-(\d{2})/;
var string = "2017-06-12";
console.log( regex.exec(string) );  // 还可以试试这个,console.log( regex.exec(string) ); 
// => ["2017-06-12", "2017", "06", "12", index: 0, input: "2017-06-12"]

也可以使用构造函数的全局属性 $1$9 来获取:

var regex = /(\d{4})-(\d{2})-(\d{2})/;
var string = "2017-06-12";

regex.test(string); // 正则操作即可,例如
//regex.exec(string);
//string.match(regex);

console.log(RegExp.$1); // "2017"
console.log(RegExp.$2); // "06"
console.log(RegExp.$3); // "12"

6.2、提取

想把 yyyy-mm-dd 格式,替换成 mm/dd/yyyy 怎么做

var regex = /(\d{4})-(\d{2})-(\d{2})/;
var string = "2017-06-12";
var result = string.replace(regex, "$2/$3/$1");
console.log(result); 
// => "06/12/2017"

6.3、非捕获分组

前面的分组都会捕获它们匹配到的数据,以便后续引用,因此也称他们是捕获型分组。 如果只想要括号最原始的功能,但不会引用它,此时可以使用非捕获分组 (?:p)

提取年份,在 6.1 的基础上

ar regex = /(\d{4})-(?:\d{2})-(?:\d{2})/;
var string = "2017-06-12";

regex.test(string); 

console.log(RegExp.$1); // "2017"
console.log(RegExp.$2); // ""
console.log(RegExp.$3); // ""

七、案例

7.1、从 html 代码中提取 imgsrc 地址

extractImg(html) {
  const imgReg = /<img[^>]+src=['"]([^'"]+)['"]+/im;
  const arr = imgReg.exec(html);
  const img = _.get(arr, '[1]', '');
  return img;
}

7.2、隐藏手机号码中间四位

hideMobile(num) {
  const reg = /^(1[3-9]{1}\d{1})\d{4}(\d{4})$/;
  return num.replace(reg, '$1****$2');
}

Reference

felix-cao commented 3 years ago

量词 {} 和分组 () 在 字符类 [] 中是没有意义的,不需要加 \,