精通正则表达式 - JavaScript的实现和应用

1. 正则表达式对象

  • 模式
    / pattern /flags
  • pattern 是任何简单或复杂的正则表达式,可以包含字符类,限定符,分组,向前查找以及反向引用。
  • flags 是匹配模式标明正则表达式的行为
    g: 表示全局模式, 模式被应用于所有字符串,而非在发现第一个匹配项时立即停止。
    i: 表示不区分大小写, 确定匹配项时忽略模式与字符串的大小写。
    m: 表示多行模式, 即在达到一行文本末尾时还会继续查找下一行中是否存在与模式匹配的项,
    ^和$匹配的开始或结束输入字符串中的每一行,而不是整个字符串的开始或结束。
    s: 表示单行模式,即允许 . 匹配换行符
    u: 使用unicode码的模式进行匹配
    y: 执行粘性搜索,匹配从目标字符串的当前位置开始
  • 元字符
    ^ $ . * + ? = ! : | \ / () [] {}
    如果要将这些元字符当作普通字符使用,则必须对它们进行转义。

  • 字符类

[...]   位于括号之内的任意字符
[^...]  不在括号之内的任意字符
.       除换行符和其他Unicode行终止符号之外的任意字符
\w      任何ASCII单子字符,等价于[a-zA-Z0-9_]
\W      任何非ASCII单子字符,等价于[^a-zA-Z0-9_]
\s      任何Unicode空白符
\S      任何非Unicode空白符的字符
\d      任何ASCII数字,等价于[0-9]
\D      除了ASCII数字之外的任何字符,等价于[^0-9]
[\b]    退格直接量
  • 量词
{n, m} 匹配前一项至少n次,但是不超过m次
{n, }  匹配前一项n次,或更多次
{n}    匹配前一项恰好n次
?      匹配前一项0次或1次,也就是说前一项是可选。等价于{0, 1}
+      匹配前一项1次或多次。等价于{1, }
*      匹配前一项0次或多次。等价于{0, }
  • 选择,分组和引用
|       选择: 匹配的是该符号左边的子表达式或右边的子表达式
(...)   分组: 将几个项目组合为一个单元,并捕获这个组合匹配的字符以供后面引用
(?:...) 只组合,不捕获
\n      引用第n个分组匹配的字符,组号是从左到右计数的左括号数,以(?:...) 形式分组的不计入编号
(?<Name>...) 命名分组, \k<Name> 引用命名分组的捕获, 也可使用\n 编号进行引用
  • 锚字符(匹配指定的位置)
^     匹配字符串的开头,在多行检索中,匹配一行的开头
$     匹配字符串的结尾,在多行检索中,匹配一行的结尾
\b    匹配一个词语的边界, 在字符组中则表示一个退格符 [\b]
\B    匹配非词语边界的位置
(?=p) 肯定顺序环视,要求子表达式左边的字符与模式p匹配,且不占用字符
(?!p) 否定顺序环视,要求子表达式右边的字符不与模式p匹配,且不占用字符
(?<=p)肯定逆序环视,要求子表达式左边的字符与模式p匹配,且不占用字符 
(?<!p)否定逆序环视,要求子表达式左边的字符不与模式p匹配,且不占用字符
  • 贪婪匹配(尽可能匹配更多的字符)

?+ * {m,n}

const matches = /a+/.exec('aaa');
console.log(matches[0]); // aaa

const matches = /a{1,3}/.exec('aaa');
console.log(matches[0]);  // aaa
  • 惰性匹配(尽可能匹配更少的字符)

?? +? \*? {m,n}?

const matches = /a+?/.exec('aaa');
console.log(matches[0]); // a

const matches = /a{1,3}?/.exec('aaa');
console.log(matches[0]);  // a 

2. 对象字面量方式创建正则表达式对象

const reg = /at/g

// 匹配字符串中所有"at"
const reg = /at/g 
// 匹配第一个"bat" 或者 "cat", 不区分大小写
const reg = /[bc]at/i;
// 匹配第一个"[bc]at", 不区分大小写 
const reg = /\[bc\]at/i;
// 匹配所有以"at"结尾的3个字符组合,不区分大小写
const reg = /.at/gi; 

3. RegExp 构造函数创建正则表达式对象

const reg = new RegExp("at", "g")

// 匹配第一个"bat" 或者 "cat", 不区分大小写
const reg = new RegExp("[bc]at", "i");

RegExp构造函数的两个参数都是字符串,在某些情况下需要对第一个模式参数进行双重转义。
从下面的对比可以看出,字面量模式更加间接,和易用使用。

字面量模式       等价的字符串
/\[bc\]at/       "\\[bc\\]at"
/\.at/           "\\.at"
/name\/age/      "name\\/age"
/\d.\d{1,2}/     "\\d.\\d{1,2}"
/\w\\hello\\123/ "\\w\\\\hello\\\\123"

4. 正则表达式对象的方法

  • exec 方法
    1. exec()接受一个字符串参数,然后返回包含第一个匹配项信息的数组,
      或者在没有匹配项的情况下返回null。
    2. 数组包含index和input两个属性,index表示匹配项在字符串中的位置,
      input则表示应用正则表达式 的字符串。
    3. 数组中第一项是与整个模式匹配的字符串,其他项是与模式中的捕获组匹配的字符串。
      如果模式中没有捕获组,则数组只包含一项。
const text = "mom and dad and baby";
const reg = /mom( and dad( and baby)?)?/gi;
const matches = reg.exec(text);

console.log(matches[0]); // "mom and dad and baby"
console.log(matches[1]); // " and dad and baby"
console.log(matches[2]); // " and baby"
console.log(matches.index); // 0
console.log(matches.input); // "mom and dad and baby"

注意:

  • 对于exec()方法而言,即使在模式中设置了全局标志(g),它每次也只返回一个匹配项。
  • 在不设置全局标志的情况下,在同一个字符串上多次调用exec()将始终返回第一个匹配项
    的信息。
  • 在设置全局标志的情况下,每次调用exec()则会在字符串中继续查找新匹配项。
const text = 'cat, bat, sat, fat';
/** no flag 'g' **/
let reg = /.at/;
let matches = reg.exec(text);
console.log(matches[0]); // "cat"
console.log(matches.index); // 0
console.log(reg.lastIndex); // 0

matches = reg.exec(text);
console.log(matches[0]); // "cat"
console.log(matches.index); // 0
console.log(reg.lastIndex); // 0

/** has flag 'g' **/
let reg = /.at/g;
let matches = reg.exec(text);
console.log(matches[0]); // "cat"
console.log(matches.index); // 0
console.log(reg.lastIndex); // 3

matches = reg.exec(text);
console.log(matches[0]); // "bat"
console.log(matches.index); // 5
console.log(reg.lastIndex); // 8

遍历正则表达式在全局模式匹配下的每一项

const text = 'cat, bat, sat, fat';
const reg = /.at/g;
let matches = null;
while ((matches = reg.exec(text)) !== null){
  console.log(matches[0]);
  console.log(matches.index);
  console.log(reg.lastIndex);
}
  • test 方法
    1. 它接受一个字符串参数,匹配的情况下返回true否则返回false
    2. 在非全局模式下,每次执行test 方法后,正则表达式对象的lastIndex被重设为0, 表示下次执行从头开始
    3. 在全局模式下,每次执行test 方法后,正则表达式对象的lastIndex被设置为紧接着匹配之后那个字符的位置,
      表示下次执行时从匹配之后的位置开始匹配。
const text = 'cat, bat, sat, fat';
/** no flag 'g' **/
let reg = /.at/;
let result  = reg.test(text);
console.log(result); // true
console.log(reg.lastIndex); // 0
result  = reg.test(text);
console.log(result); // true
console.log(reg.lastIndex); // 0

/** has flag 'g' **/
let reg = /.at/g;
let result  = reg.test(text);
console.log(result); // true
console.log(reg.lastIndex); // 3
result  = reg.test(text);
console.log(result); // true
console.log(reg.lastIndex); // 8

注意:

  • 使用一个全局模式的正则表达式去匹配多个字符串时,在匹配一个新字符串前需要确保
    正则表达式对象的lastIndex属性为0, 否则在匹配字符串时就会从新字符串的某个中间
    位置开始匹配,导致匹配错误。
  • 使用一个全局模式的正则表达式匹配多个字符串时,通过循环重复调用正则表达式的方法
    找到所有匹配项,以便lastIndex属性自动置为0, 否则需要手动置为0,以便对新的字符串
    进行模式匹配。

如果lastIndex属性没有设置为0,则匹配text2时,第一次会匹配到fat而不是预期的sat

const text1 = 'cat, bat';
const text2 = 'sat, fat';
const reg = /.at/g;
const matches1 = reg.exec(text1);
console.log('matches1', matches1[0]); //cat
console.log(reg.lastIndex);
// reg.lastIndex = 0;
const matches2 = reg.exec(text2);
console.log('matches2', matches2[0]); //fat

5. 正则表达式对象属性

  • global: 布尔值,表示是否设置了g 标志
  • ignoreCase: 布尔值, 表示是否设置了i标志
  • lastIndex: 整数,表示开始搜索下一个匹配项的字符位置,从0算起。
  • multiline: 布尔值,表示是否设置了m标志
  • source: 正则表达式的字符串表示,按照字面量模式返回的字符串而非构造函数参数的字符串模式。
const reg = /\[bc\]at/i;
console.log(reg.global);    // false
console.log(reg.ignoreCase);// true
console.log(reg.multiline); // false
console.log(reg.lastIndex); // 0
console.log(reg.source);    // "\[bc\]at"

6. RegExp构造函数属性(已被废弃,不建议使用)

长属性名 短属性名 说明
input $_ 最近一次要匹配的字符串
lastMatch $& 最近一次的匹配项
lastParen $+ 最近一次匹配的捕获组
leftContent $` input 字符串中lastMatch之前的文本
rightContent $' input 字符串中lastMatch之后的文本
const text = "this has been a short summer";
const reg = /(.)hort/g;
reg.test(text);
console.log("input: ", RegExp.input ); // input:  this has been a short summer
console.log("$_: ", RegExp["$_"] ); // $_:  this has been a short summer
console.log("leftContext: ", RegExp.leftContext ); // leftContext:  this has been a
console.log("$`: ", RegExp["$`"]); // $`:  this has been a
console.log("rightContext: ", RegExp.rightContext); // rightContext:  summer
console.log("$`: ", RegExp["$'"]); // $`:   summer
console.log("lastMatch: ", RegExp.lastMatch); // lastMatch:  short
console.log("$&: ", RegExp["$&"]); // $&:  short
console.log("lastParen: ", RegExp.lastParen); // lastParen:  s
console.log("$+: ", RegExp["$+"]);  // $+: s

$1 $2 ... $9 分别存储第1到第9的捕获组

const text = "this has been a short summer";
const reg = /(.)hort/g;
reg.test(text);
console.log("$1: ", RegExp.$1);  // $1: s
posted @ 2022-11-25 17:20  箫笛  阅读(40)  评论(0)    收藏  举报