零宽:只匹配位置,在匹配过程当中,不占用字符,因此被称为零宽git
先行:正则引擎在扫描字符的时候,从左往右扫描,匹配扫描指针未扫描过的字符,先于指针,故称先行github
后行:匹配指针已扫描过的字符,后于指针到达该字符,故称后行,即产生回溯正则表达式
正向:即匹配括号中的表达式api
负向:不匹配括号中的表达式ui
es5 就支持了先行断言google
es2018 才支持后行断言url
注意:
.
在正则里面表明匹配除换行符,回车符等少数空白字符以外的任何字符,匹配其时须要转义
(?=pattern):某位置后面紧接着的字符序列要匹配 patternes5
例:spa
`sinM.`.match(/sin(?=M\.)/g); // ["sin"]
`M.sin`.match(/sin(?=M\.)/g); // null
复制代码
第一个 sin 会匹配,由于他后面有 pattern指针
(?!pattern):某位置后面紧接着的字符序列不能匹配 pattern
例:
`M.sin`.match(/sin(?!M\.)/g); // ["sin"]
`sinM.`.match(/sin(?!M\.)/g); // null
复制代码
第一个 sin 会匹配,由于他后面没有 pattern
(?<=pattern):某位置前面紧接着的字符序列要匹配 pattern
例:
'sinM.'.match(/(?<=M\.)sin/g); // null
'M.sin'.match(/(?<=M\.)sin/g); // ["sin"]
复制代码
第二个 sin 会匹配,由于它前面有 pattern
(?<!pattern):某位置前面紧接着的字符序列不能匹配 pattern
例:
'sinM.'.match(/(?<!M\.)sin/g); // ["sin"]
'M.sin'.match(/(?<!M\.)sin/g); // null
复制代码
第一个 sin 会匹配,由于它前面没有 pattern
来看个实际的例子,把4+6*sqrt(5)*Math.sqrt(5)
转换成能够经过eval
或者new Function()
得到实际结果的字符串
这个可使用负向后行断言,即替换前面不紧接 Math.的 sqrt 字符串序列
let s = `4+6*sqrt(5)*Math.sqrt(5)`.replace(/(?<!Math\.)sqrt/g, func => `Math.${func}`);
eval(s); // 34
复制代码
第二个例子: 匹配 url 后面的路径
'https://www.google.com/v3/api/getUser?user=panghu'.match(/(?<=\.\w*(?=\/)).*/);
复制代码
第三个例子:替换字符串中 img 标签的 width 为 100%
'<img id = "23" style="width:999x;"/><img id = "23" style="width:999x;"/>'.replace(
/(?<=(<img[\s\S]*width:\s*))[^("\/);]*/gm,
'100%'
);
复制代码
匹配 sin
'M.sin'.match(/(?<=M\.)sin/g); // ["sin"]
`M.sin`.match(/sin(?!M\.)/g); // ["sin"]
复制代码
这两种方法均可以实现一样的效果,但我我的更喜欢使用第一种方法,它的写法更符合人的直接思惟习惯
先看下面两行代码的运行结果
let reg = /js/g;
reg.test('js'); //before: lastIndex:0, after: lastIndex:2
reg.test('js'); //before: lastIndex:2, after: lastIndex:0
reg.test('js'); //before: lastIndex:0, after: lastIndex:2
复制代码
若是你的答案是三个 true 的话,那就错了 答案实际上是 true、false、true,这就是所谓的怪异现象
为何?答: RegExp 对象有个 lastIndex 属性,它的初始值是 0, 当不使用 g 修饰符修饰时,每次执行 test 方法以后它都会自动置 0 而使用 g 修饰符时,每次执行 test 方法的时候,它都是从索引值为 lastIndex 的位置开始匹配,lastIndex 为匹配到的字符序列下一个索引值。只有当匹配失败之后才会将 lastIndex 置为 0
例:上述例子中的第一个 test 方法执行以前,lastIndex 值为 0,执行以后 lastIndex 值为 2,因而当第二次执行 test 方法时,从字符串索引值为 2 处开始匹配,显然会匹配失败,因此第三次匹配时又会匹配成功
<div class="root">
这里能够涉及到的知识点有:贪婪/非贪婪匹配,模式匹配,回溯及其消除,分组,反向引用
`<div class="root"><span class="root"></span><i class='root'></i></div>`.match(/<[^>]*class="root".*?>/g);
// ["<div class="root">", "<span class="root">"]
复制代码
模式匹配[^>]
表示匹配除[^]
里面的全部字符,这里就是匹配除>
外的全部字符 注意先后都须要非贪婪匹配符号?不然只有前面的,它会贪婪的吃掉 div;只有后面的,它会贪婪的吃掉 span
`<div class="root"><span class="root"></span><i class='root'></i></div>`.match(/<[^>]*class=("root"|'root').*?>/g);
// ["<div class="root">", "<span class="root">", "<i class='root'>"]
复制代码
这里若是不使用[^>]
而使用.*
就会出现下面这种匹配结果,不是咱们想要的
["<div class="root">", "<span class="root">", "</span><i class='root'>"]
("root"|'root')
,再消除.*?
回溯`<div class="root"><span class="root"></span><i class='root'></i></div>`.match(/<[^>]*class=("|')root\1[^>]*>/g);
// ["<div class="root">", "<span class="root">", "<i class='root'>"]
复制代码
\1
表示引用前面的第一个分组结果,即("|')
的匹配结果,这样就能保证单引号配对单引号,双引号匹配双引号
[^>]*
代替.*?
能够消除使用*?
引起的回溯,由于*
是尽量多的匹配,而?
是尽量少的匹配
回顾开头,我所说的特殊状况就是标签的属性值不能含有>
,由于为了消除回溯使用的[^>]
含有字符>,这部分其实可使用其余正则代替,让它在消除回溯的状况下能够匹配特殊状况
若是你们对匹配含 class 为 root 的标签这部分涉及的知识点感兴趣,能够在底下评论,我到时候再仔细讲
若是你喜欢这篇文章的话,麻烦点个⭐原文地址资瓷下
参考:
JavaScript 权威指南(第 6 版)
Javascript 正则表达式迷你书
以上若有错误,欢迎指正