两个等效语义的正则,为何输出的结果却不一致呢

李惟
  • 2.1k

代码如下:

'z/yy'.replace(/\/(y|z)|y|z/g, function(name, index) {
    console.log(index, name.length);
    return name == 'y' || name == 'z' ? 'a' : name;
});

正确输出:a/ya

'z/yy'.replace(/\/(y|z)|\1/g, function(name, index) {
    console.log(index, name.length);
    return name == 'y' || name == 'z' ? 'a' : name;
});

错误输出:z/yy

请问为什么呢?

回复
阅读 2.6k
2 个回答
武可
  • 1.4k
✓ 已被采纳

@皓矾 的console解释的比较清楚了,唯一不同意的地方是|\1不是匹配所有字符串,而是0长空字符。console里可以看出来。
这里/\/(y|z)|\1/是分为两个正则分别匹配的:
1. \/(y|z) 捕获组1是括号中y|z匹配的内容。比如/(y|z)\1/ 匹配yy,zz,但是不匹配yz
2. \1 捕获组1未定义
所以2中的\1不会如你所想的匹配y或z
比较符合你意图的写法可能是
/\/?(y|z)/

皓矾
  • 164

这两个正则是不等价的。

第一个/\/(y|z)|y|z/g,分解开来是\/(y|z)yz。所以,对于字符串z/yy,第一个字符z匹配,被替换为a,/y符合但不等于yz,所以原样输出为/y,最后一个z同理替换为a

第二个/\/(y|z)|\1/g,比较复杂,展开来说。

在此之前,先根据MDN的文档重写下函数,因为题主的参数不太正确:

"z/yy".replace(/\/(y|z)|\1/g, function(matcher, p1, offset, str) {
    console.log(matcher, p1, offset, str);
    return matcher == 'y' || matcher == 'z' ? 'a' : matcher;
});

其中,matcher是匹配的子字符串,p1是捕捉组匹配的子字符串,既(y|z)捕捉到的,offset是偏移值,str是原字符串。

执行结果如下:

//  console.log
line1 '' undefined 0
line2 /y y         1
line3 '' undefined 3
line4 '' undefined 4

一步步来,

  1. 第一行,偏移值是0,当前监测字符串是z/yymatcher为空,未匹配。
  2. 第二行,偏移值是1,当前监测字符串是/yymatcher/y,捕捉到y。但题主的返回值判断的是matacheryz的比较,所以原样返回/y
  3. 第三行,偏移值是3,当前监测字符串是ymatcher为空,未匹配。
  4. 第三行,偏移值是4,当前监测字符串是``,matcher为空,未匹配。

关键在第三行,对于字符串'y',正则仍然是/\/(y|z)|\1/g,而不是题主所想的/\/(y|z)|(y|z)/g,所以不匹配。

顺带研究了下这种|\1写法的正则,这种写法貌似会匹配任何字符串,甚至空字符串。比如:

/(x)|\1/g.test("fdaf") // return true

甚至:

/(x)|\1/g.test("") return true

所以,最好还是别这样写……

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
宣传栏