正则匹配正常但JavaScript循环替换时跳过匹配的原因
问题:正则循环匹配替换时跳过部分占位符
我有一段包含%%%NAME.suffix|DefaultValue%%%格式占位符的文本,用正则表达式%{3}((\w+)(?:\.(\w+))?)(?:\|([\s\S]*?))?%{3}可以匹配这些占位符,但在JavaScript中用while循环逐个替换时,会跳过部分匹配。我没有用String.replace(/a/gm, "b")这种全局正则替换,而是用String.replace("匹配串", "替换值")的字符串替换方式。执行以下代码后,发现Y占位符被跳过:
var soup = "%%%X.x|a%%% %%%Y.y|b%%% %%%Z.z|c%%%"; var m; while ((m = regex.exec(soup)) !== null) { // 避免零宽匹配导致无限循环 if (m.index === regex.lastIndex) { regex.lastIndex++; } console.log(m[2] + " found: " + JSON.stringify(m, null, 2)); soup = soup.replace(m[0], m[4]); }
输出结果:
X found: [ "%%%X.x|a%%%", "X.x", "X", "x", "a" ] Z found: [ "%%%Z.z|c%%%", "Z.z", "Z", "z", "c" ]
原因分析
核心问题出在正则的lastIndex属性和字符串修改后的索引错位:
- 正则开启了
g(全局匹配)标志,exec()会从lastIndex指定的位置开始匹配。 - 第一次匹配到
%%%X.x|a%%%后,lastIndex被设为该匹配串的结束位置(即0 + "%%%X.x|a%%%".length = 11)。 - 执行
soup.replace(m[0], m[4])后,原字符串变成"a %%%Y.y|b%%% %%%Z.z|c%%%",长度变短,但正则的lastIndex仍保持11。 - 第二次
exec()从索引11开始查找,此时已经跳过了%%%Y.y|b%%%的位置,直接匹配到了后面的Z。
解决方法
方法1:每次替换后重置lastIndex
修改字符串后,把正则的lastIndex重置为0,让下一次exec()从头开始匹配:
var soup = "%%%X.x|a%%% %%%Y.y|b%%% %%%Z.z|c%%%"; var m; while ((m = regex.exec(soup)) !== null) { if (m.index === regex.lastIndex) { regex.lastIndex++; } console.log(m[2] + " found: " + JSON.stringify(m, null, 2)); soup = soup.replace(m[0], m[4]); regex.lastIndex = 0; // 重置匹配起始位置 }
方法2:改用正则全局替换(更简洁)
直接利用正则的全局匹配特性,用String.replace()的回调写法完成替换,无需手动循环:
var soup = "%%%X.x|a%%% %%%Y.y|b%%% %%%Z.z|c%%%"; var regex = /%{3}((\w+)(?:\.(\w+))?)(?:\|([\s\S]*?))?%{3}/g; soup = soup.replace(regex, function(match, p1, p2, p3, p4) { console.log(p2 + " found: " + JSON.stringify([match, p1, p2, p3, p4], null, 2)); return p4; });
内容的提问来源于stack exchange,提问作者droptix
相关产品推荐
相关产品推荐

