可以匹配空字符串的正则表达式破坏了 javascript 正则表达式引擎

IT技术 javascript regex
2021-02-26 03:15:22

我写了以下正则表达式: /\D(?!.*\D)|^-?|\d+/g

我认为它应该这样工作:

\D(?!.*\D)    # match the last non-digit
|             # or
^-?           # match the start of the string with optional literal '-' character
|             # or
\d+           # match digits

但是,它没有:

var arrTest = '12,345,678.90'.match(/\D(?!.*\D)|^-?|\d+/g);
console.log(arrTest);

var test = arrTest.join('').replace(/[^\d-]/, '.');
console.log(test);

然而,播放时将其PCRE(php)-flavour在网上Regex101它按我的描述工作。

我不知道我是否认为它应该以一种不起作用的方式起作用。或者,如果 javascript regex-flavor 中不允许某些模式。

2个回答

JS 的工作方式与 PCRE 不同。关键是 JS 正则表达式引擎不能很好地处理零长度匹配,索引只是手动递增,并跳过零长度匹配后的下一个字符。^-?可以匹配一个空字符串,它的匹配12,345,678.90开始,跳过1

如果我们查看String#match文档,我们将看到找到零长度匹配,每次调用matchwith 全局正则表达式都会增加正则表达式对象的lastIndex

  1. 否则,globaltrue
    a。使用参数“ lastIndex ”和 0调用 rx 的 [[Put]] 内部方法。
    b.让 A 成为一个新数组,就像由表达式new Array()创建的一样,其中Array是具有该名称的标准内置构造函数。
    C。previousLastIndex为 0。
    d.n为 0
    lastMatchtrue
    F。重复,而lastMatch
        i。result为调用exec的 [[Call]] 内部方法的结果rx作为包含Sthis值和参数列表     ii. 如果resultnull,则将lastMatch设置false     三、否则,结果不为         1. 让thisIndex是调用rx的 [[Get]] 内部方法的结果,参数为“ lastIndex ”。         2. 如果thisIndex = previousLastIndex             a. 使用参数“ lastIndex调用rx的 [[Put]] 内部方法




    thisIndex+1
                湾 previousLastIndex设置thisIndex +1。

因此,匹配过程从8a8f初始化辅助结构,然后进入一个 while 块(重复直到lastMatchtrue,内部exec命令匹配字符串开头的空格(8fi -> 8fiii),并且由于结果不为nullthisIndex被设置为上一个成功匹配lastIndex,并且由于匹配长度为零(基本上,thisIndex = previousLastIndex),previousLastIndex被设置为thisIndex+1 -在成功的零长度匹配后跳过当前位置

您实际上可以在replace方法中使用更简单的正则表达式并使用回调来使用适当的替换:

var res = '-12,345,678.90'.replace(/(\D)(?!.*\D)|^-|\D/g, function($0,$1) {
   return $1 ? "." : "";
});
console.log(res);

图案详情

  • (\D)(?!.*\D) - 一个非数字(捕获到第 1 组中),除了换行符和另一个非数字外,后面没有 0+ 个字符
  • | - 或者
  • ^- - 字符串开头的连字符
  • | - 或者
  • \D - 非数字

请注意,此处您甚至不必将开头的连字符设为可选。

我花了 16 分钟来添加(和格式化)ECMA 参考,并逐步解释为什么会发生这种情况。希望它能解释这种奇怪的 JS全局正则表达式匹配行为。请注意,PCRE 不会在零长度匹配后直接增加位置,它会重新正确检查下一个字符。
2021-04-18 03:15:22
谢谢:) ...我最终.replace(/(^-)|\D(?=.*\D)|(\D)/g, function($0, $1, $2) { return $1 || ($2 ? '.' : ''); });避免替换负号。
2021-04-26 03:15:22
如果你不需要替换那个减号,为什么要使用替代分支:)?只需(^-)|从模式中移除 并$2从回调函数中移除并调整其主体。
2021-05-12 03:15:22

您可以重新排序交替模式并在 JS 中使用它来使其工作:

var arrTest = '12,345,678.90'.match(/\D(?!.*\D)|\d+|^-?/g);
console.log(arrTest);

var test = arrTest.join('').replace(/\D/, '.');

console.log(test);

//=> 12345678.90

正则表达式演示

这是 Javascript 和 PHP(PCRE) 正则表达式行为之间的区别。

在 JavaScript 中:

'12345'.match(/^|.+/gm)
//=> ["", "2345"]

在 PHP 中:

preg_match_all('/^|.+/m', '12345', $m);
print_r($m);
Array
(
    [0] => Array
        (
            [0] =>
            [1] => 12345
        )
    )

因此,当您^在 Javascript 中匹配时,正则表达式引擎会向前移动一个位置,并且|在输入中从第二个位置开始交替匹配之后的任何内容

这是因为当您^在 JS 正则表达式引擎中匹配时,会将|输入中的第二个位置向前移动一个位置,并且交替匹配之后的任何内容测试'12345'.match(/^|.+/gm)哪个会给["", "2345"]
2021-04-17 03:15:22