JS正则表达式全局匹配的那些坑

7,791 阅读3分钟

前段时间工作中遇到了正则全局匹配模式下的一些坑,在此查找总结了一下,以供参考。

什么是全局匹配

如果我们期望重复匹配目标字符串多次,可以使用 /.../g 或创建new RegExp(..., 'g') 全局匹配,这样正则表达式的global属性将是true

let reg = /test/g;
reg.global // true

全局的正则表达式另外一个属性lastIndex表示上一次匹配文本结果之后的第一个字符的位置,上次匹配的结果是由方法 RegExp.prototype.exec()RegExp.prototype.test() 找到的,它们都以 lastIndex 属性所指的位置作为下次检索的起始点。这样,就可以通过反复调用这两个方法来遍历一个字符串中的所有匹配文本。lastIndex属性是可读可写的,只要目标字符串的下一次搜索开始,就可以对它进行设置。当方法 RegExp.prototype.exec()RegExp.prototype.test() 再也找不到可以匹配的文本时,它们会自动把 lastIndex 属性重置为 0。

RegExp.prototype.test():检测是否存在匹配

对于非全局的正则表达式,test()只会检测是否存在某个目标字符串,多次检测的结果都相同,例如:

let reg = /test/;
let str = '_test_test';
reg.test(str) // true
reg.test(str) // true

当设置全局标志 /g 时,一旦字符串中还存在匹配,test() 方法都将返回 true,同时匹配成功后将把 lastIndex 属性的值设置为上次匹配成功结果之后的第一个字符所在的位置,下次匹配将从 lastIndex 指示的位置开始;匹配不成功时返回 false,同时将 lastIndex 属性的值重置为 0。

let reg = /test/g;
let str = '_test_test';

reg.test(str) // true
reg.lastIndex // 5

reg.test(str) // true
reg.lastIndex // 10

reg.test(str) // false
reg.lastIndex // 0

RegExp.prototype.exec():捕获指定的字符串

如果没有设置全局项 /g,该方法将始终返回第一个匹配项:

let reg = /test/;
let str = '_test_test';

reg.exec(str) // ["test", index: 1, input: "_test_test", groups: undefined]
reg.lastIndex // 0

reg.test(str) // ["test", index: 1, input: "_test_test", groups: undefined]
reg.lastIndex // 0

当全局匹配时,该方法每次返回一个匹配项,直到没有匹配项时返回 null

let reg = /test/g;
let str = '_test_test';

reg.exec(str) // ["test", index: 1, input: "_test_test", groups: undefined]
reg.lastIndex // 5

reg.test(str) // ["test", index: 6, input: "_test_test", groups: undefined]
reg.lastIndex // 10

reg.test(str) // null
reg.lastIndex // 0

String.prototype.search():查找匹配位置

该方法将忽略全局设置项,简单地返回首次匹配的位置:

let regex = /test/;
let str = '_test_test';
str.search(regex); // 1

let regex = /test/g;
let str = '_test_test';
regex.lastIndex;   //  0
str.search(regex); // 1
regex.lastIndex;   //  0,因为该方法忽略了全局设置项

String.prototype.match():找到一个或多个正则表达式的匹配

非全局匹配时,多次执行结果一样,都返回首次匹配结果,忽略 lastIndex

let regex = /test/;
let str = '_test_test';
str.match(regex); // ["test", index: 1, input: "_test_test"] 
regex.lastIndex   // 0
str.match(regex); // ["test", index: 1, input: "_test_test"]
regex.lastIndex   // 0

全局匹配时,该方法返回所有匹配结果,并忽略 lastIndex

let regex = /test/g;
let str = '_test_test';
str.match(regex); // ["test", "test"]
regex.lastIndex   // 0
str.match(regex); // ["test", "test"]
regex.lastIndex   // 0
str.match(regex); // ["test", "test"]
regex.lastIndex   // 0

String.prototype.replace():替换与正则表达式匹配的子串

如果没有设置全局匹配,那么将替换首次匹配的位置;如果设置了全局匹配,那么将替换所有匹配位置:

// 非全局匹配
'_test_test'.replace(/test/, 'r');  // '_r_test'
// 全局匹配
'_test_test'.replace(/test/g, 'r'); // '_r_r'

参考链接:bubkoo.com/2014/03/19/…