正则冷知识

128 阅读3分钟

贪婪模式惰性模式的区别

贪婪匹配是先看整个字符串是否匹配,如果不匹配,它会去掉字符串的最后一个字符,并再次尝试,如果还不匹配,那么再去掉当前最后一个,直到发现匹配或不剩任何字符。
惰性匹配是从左侧第一个字符开始向右匹配, 先看第一个字符是不是一个匹配, 如果不匹配就加入下一个字符再尝式匹配, 直到发现匹配。

模式说明
{n,m}连续出现 n 到 m 次。贪婪模式
{n,}至少连续出现 n 次。贪婪模式
{n}连续出现 n 次。贪婪模式
?等价于 {0,1}。贪婪模式
+等价于 {1,}。贪婪模式
_等价于 {0,}。贪婪模式

量词后面加上问号后如下:

模式说明
{n,m}?连续出现 n 到 m 次。惰性模式
{n,}?至少连续出现 n 次。惰性模式
{n}?连续出现 n 次。惰性模式
??等价于 {0,1}?。惰性模式
+?等价于 {1,}?。惰性模式
_?等价于 {0,}?。惰性模式

回溯

正则表达式匹配目标字符串时,它从左到右逐个测试表达式的组成部分,看是否能找到匹配项。在遇到量词时,需要决定何时尝试匹配更多字符。在遇到分支时,必须从可选项中选择一个尝试匹配。每当正则做类似的决定时,如果有必要,都会记录其他选择,以便匹配不成功时进行回溯,到最后一个决策点,再重新进行匹配。回溯失控时会导致匹配过慢。

断言

包括正先行断言(前瞻)(?=X)、负先行断言(前瞻)(?!X)、正后发断言(后顾)(?<=X)、负后发断言(后顾)(?<!X),断言简单来说是一个匹配条件而不参与匹配内容。

语法说明
(?=X)正先行断言。仅当子表达式 X 在此位置的右侧匹配时才继续匹配。例如,/w+(?=\d)/ 与后面跟数字的单词匹配,而不与该数字匹配,此构造不会回溯
(?!X)负先行断言。仅当子表达式 X 不在此位置的右侧匹配时才继续匹配。例如,/w+(?!\d)/ 与后面不跟数字的单词匹配,而不与该数字匹配
(?<=X)正后发断言。仅当子表达式 X 在此位置的左侧匹配时才继续匹配。例如,/(?<=19)99/ 与跟在 19 后面的 99 的实例匹配,此构造不会回溯
(?<!X)负后发断言。仅当子表达式 X 不在此位置的左侧匹配时才继续匹配。例如,/(?<!19)99/ 与不跟在 19 后面的 99 的实例匹配
'yx999'.replace(/x(?=(\d{3}))/g, 'P'); // yP999
'y999x'.replace(/(?!(\d{3}))x/g, 'P'); // y999P
'y999x'.replace(/(?<=(\d{3}))x/g, 'P'); // y999P
'yx999'.replace(/x(?<!(\d{3}))/g, 'P'); // yP999

捕获分组

  • 正常只使用括号就是捕获模式,可以捕获括号里的数据,保存在内存中,会占用更多内存
  • 非捕获模式,就是在里面的最前面加?:,RegExp 不会保存该分组的数据
let regex1 = /(\d{4})-(\d{1,2})-(\d{2})/;
regex1.test('2030-10-1'); // true
RegExp.$1; // "2030"
RegExp.$2; // "10"
RegExp.$3; // "1"

let regex2 = /(?:\d{4})-(\d{1,2})-(?:\d{2})/;
regex2.test('2030-10-1'); // true
RegExp.$1; // "10"
RegExp.$2; // ""

边界

正则表达式中:\b表示单词边界,\B表示非单词边界,应理解为(非单词)边界,而不是非(单词边界),它仍然匹配的是边界。
正则中所说的单词指的是\w可以匹配的字符,即数字、大小写字母以及下划线[0-9a-zA-Z_]。
将正则中的位置分为字符的占位和字符的间隙。字符的占位是显式的位置。
字符的间隙是隐式的位置。而边界指的是占位的字符左右的间隙位置。

\b 单词边界

\b是正则表达式规定的一个特殊代码,代表着单词的开头或结尾,也就是单词的分界处。虽然通常英文的单词是由空格,标点符号或者换行来分隔的,但是 \b 并不匹配这些单词分隔字符中的任何一个,它只匹配一个位置。
\b匹配这样的位置:它的前一个字符和后一个字符不全是(一个是,一个不是或不存在)\w

// 只有首尾位置匹配
console.log('0aZ_'.replace(/\b/g, '.')); // .0aZ_.

// +不是\w,所以它的左右间隙都可以被匹配
console.log('a+a'.replace(/\b/g, '.')); // .a.+.a.

// 空格也不是\w,所以它的左右间隙都可以被匹配
console.log('a a'.replace(/\b/g, '.')); // .a. .a.

// 举例:
const str = "It's a nice day today";
// 正确的正则
console.log(str.replace(/\bnice\b/g, '.')); // "It's a . day today"
// 错误的正则
console.log(str.replace(/a\bnice/g, '.')); // "It's a nice day today"
// 纠正的正则
// '.'匹配除换行符(\n、\r)之外的任何单个字符, '\s'匹配一切空白符,或者直接使用空格' '也可
console.log(str.replace(/a\b.\bnice/g, '.')); // "It's . day today"

\B 非单词边界

\B匹配的也是边界 ,针对的是与\b相反的非单词\W,即所有不能被\b匹配的边界。

console.log('0aZ_'.replace(/\B/g, '.')); // 0.a.Z._
console.log('a+a'.replace(/\B/g, '.')); // a+a
console.log('a a'.replace(/\B/g, '.')); // a a

// 价格匹配
// (?=(\d{3})+(?!\d)) 声明匹配一个或多个三位数的组,且它们右侧为非数字,不理解的话请回顾上面的断言
'18888.99'.replace(/\B(?=(\d{3})+(?!\d))/g, ','); // '18,888.99';