例如,我们通过一个函数把一个 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,
先说结论,区别在于
?=
是正向肯定 断言,进行的匹配是不占查询长度的;而?:
是非获取 匹配,进行的匹配是占据查询长度的。题述的正则查询每一个非单词边界,然后对后面的一个或多个连续三组数字+一组非数字进行匹配。对于 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 全替换成了,
,然后完成了匹配。所以就出现了上面的结果。