正则表达式中 ?= 和 ?: 的区别

例如,我们通过一个函数把一个 number 类型的数字转换成一个字符串,并且每三位给他加上一个 ',';
1999 -> 1,999

先看看 (?=pattern) 的使用,下面这个是正确的:

function groupByCommas(n) {
  return n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
console.log(groupByCommas(1234567));    //1,234,567

如果我们删掉(\d{3})后面的 '+'的话,全局标志依然还在,但是这个时候,就只能匹配字符串中的部分了。

function groupByCommas(n) {
  return n.toString().replace(/\B(?=(\d{3})(?!\d))/g, ",");
}
console.log(groupByCommas(1234567));    //1234,567

我的看法是这样的:
正则表达式等价于

/\B(?=(\d{3}){1}(?!\d))/g

所以当匹配到匹配项的时候,index 的位置已经到了4与5之间,而前面的正则是通过

/\B(?=(\d{3}){1}(?!\d))/g
/\B(?=(\d{3}){2}(?!\d))/g

匹配。

最后如果我们把 ?= 换成 ?: 的话:

function groupByCommas(n) {
  return n.toString().replace(/\B(?:(\d{3})+(?!\d))/g, ",");
}
console.log(groupByCommas(1234567));    //1,
阅读 20.3k
3 个回答

先说结论,区别在于 ?= 是正向肯定 断言,进行的匹配是不占查询长度的;而 ?: 是非获取 匹配,进行的匹配是占据查询长度的。

题述的正则查询每一个非单词边界,然后对后面的一个或多个连续三组数字+一组非数字进行匹配。对于 1234567 而言,就会匹配到 1 和 2 中间的这个非单词边界,因为后面的 234567$ 满足正向肯定预查的 (\d{3})+(?!\d) 形式;之后会匹配到 4 和 5 中间的非单词边界,因为后面的 567$ 也满足上一形式。所以是正确的。

而你尝试将 + 去掉,使得断言只能匹配到 567$ 这样的形式——注意到你强调了 g 全局查询参数,但是我们要注意到 (?!\d) 的存在,这是一个正向否定断言,表示连续三个数字之后不能存在数字,所以 234 显然是不满足的,因为其后的 5 正是一个数字。假使你去掉了这个否定断言,那这个正则也不能工作——因为断言是 零宽 的,是不占据匹配长度的,查完 1 之后 234 满足,还会继续查 2,2 之后 345 也是满足的。因此结果就会变成 "1,2,3,4,567"

最后你尝试使用了 ?: 这个非获取匹配实际上是占据匹配长度的,当执行了第一次匹配时,实际上就匹配到了行尾,直接将 234567 全替换成了 ,,然后完成了匹配。所以就出现了上面的结果。

(?=exp)也叫零宽度正预测先行断言,它断言自身出现的位置的后面能匹配表达式exp

(?:exp)表示非捕获性分组,它不会存在匹配成功后的分组里

可以看下我在这里对?=的解释

1 篇内容引用
宣传栏