为什么“ asdf”. place (/. */g,“ x”) = = “ xx”?

我偶然发现了一个令我惊讶的事实。

console.log("asdf".replace(/.*/g, "x"));

Why two replacements? It seems any non-empty string without newlines will produce exactly two replacements for this pattern. Using a replacement function, I can see that the first replacement is for the entire string, and the second is for an empty string.

6497 次浏览

第一个匹配显然是 "asdf"(位置[0,4])。因为设置了全局标志(g) ,所以它继续搜索。此时(位置4) ,它找到第二个匹配项,一个空字符串(位置[4,4])。

请记住,*匹配零个或多个元素。

根据 ECMA-262标准,原型,替换调用 原型[@@ place ],它说:

11. Repeat, while done is false
a. Let result be ? RegExpExec(rx, S).
b. If result is null, set done to true.
c. Else result is not null,
i. Append result to the end of results.
ii. If global is false, set done to true.
iii. Else,
1. Let matchStr be ? ToString(? Get(result, "0")).
2. If matchStr is the empty String, then
a. Let thisIndex be ? ToLength(? Get(rx, "lastIndex")).
b. Let nextIndex be AdvanceStringIndex(S, thisIndex, fullUnicode).
c. Perform ? Set(rx, "lastIndex", nextIndex, true).

其中 rx/.*/gS'asdf'

见11.c. iii. 2. b:

设 nextIndex 为 AdvanceStringIndex (S,this Index,fullUnicode)。

因此,在 'asdf'.replace(/.*/g, 'x')中,实际上是:

  1. Result (未定义) ,result = [],lastIndex = 0
  2. Result = 'asdf',result = [ 'asdf' ],lastIndex = 4
  3. Result = '',result = [ 'asdf', '' ],lastIndex = 4AdvanceStringIndex,将 lastIndex 设置为 5
  4. Result = null,result = [ 'asdf', '' ],return

因此有两个匹配。

在与 糟糕的离线聊天中,我们发现了一个 直觉的方式,可以看出为什么 "abcd".replace(/.*/g, "x")确切地产生两个匹配。注意,我们还没有检查它是否完全等同于 ECMAScript 标准所强加的语义,因此只是将其作为一个经验法则。

经验法则

  • 中的元组 (matchStr, matchIndex)列表 指示输入字符串的哪些字符串部分和索引已经被吃掉的时间顺序。
  • 这个列表是从正则表达式的输入字符串的左侧开始不断构建的。
  • 已经被吃掉的部分再也无法匹配了
  • 替换是在 matchIndex给出的索引下完成的,在该位置覆盖子字符串 matchStr。如果是 matchStr = "",那么“替换”就是有效的插入。

形式上,匹配和替换操作被描述为一个循环,如 另一个答案所示。

简单例子

  1. 产出 "xx":

    • 比赛名单是 [("abcd", 0), ("", 4)]

      值得注意的是,没有确实包括下列比赛,出于以下原因,人们可以想到这些比赛:

      • 量词 *是贪婪的
      • ("b", 1)("bc", 1): 由于之前的比赛 ("abcd", 0),字符串 "b""bc"已经吃掉了
      • ("", 4), ("", 4)(即两次) : 指数位置4已经被第一个明显的匹配吞噬
    • 因此,替换字符串 "x"将在这些位置精确地替换找到的匹配字符串: 在位置0,它将替换字符串 "abcd",在位置4,它将替换 ""

      这里您可以看到替换可以作为前一个字符串的真正替换,或者仅仅作为新字符串的插入。

  2. 具有 惰性量词 *?输出的 "abcd".replace(/.*?/g, "x")

    • 比赛名单是 [("", 0), ("", 1), ("", 2), ("", 3), ("", 4)]

      与前面的示例不同,这里不包括 ("a", 0)("ab", 0)("abc", 0),甚至 ("abcd", 0),因为量词的惰性严格限制它寻找尽可能短的匹配。

    • 由于所有匹配字符串都是空的,因此不会发生实际替换,而是在位置0、1、2、3和4处插入 x

  3. 具有 惰性量词 +?输出的 "abcd".replace(/.+?/g, "x")

    • 比赛名单是 [("a", 0), ("b", 1), ("c", 2), ("d", 3)]
  4. 具有 惰性量词 [2,}?输出的 "abcd".replace(/.{2,}?/g, "x")

    • 比赛名单是 [("ab", 0), ("cd", 2)]
  5. "abcd".replace(/.{0}/g, "x")通过与示例2相同的逻辑输出 "xaxbxcxdx"

更难的例子

如果我们总是匹配一个空字符串,并控制这种匹配发生在对我们有利的位置,我们就可以始终如一地利用 插入而不是替换的思想。例如,我们可以创建与每个偶数位置的空字符串匹配的正则表达式,以便在那里插入一个字符:

  1. "abcdefgh".replace(/(?<=^(..)*)/g, "_"))具有 正面看待 (?<=...)输出 "_ab_cd_ef_gh_"(目前为止只在 Chrome 中支持)

    • 比赛名单是 [("", 0), ("", 2), ("", 4), ("", 6), ("", 8)]
  2. 具有 积极的前瞻 (?=...)输出的 "abcdefgh".replace(/(?=(..)*$)/g, "_"))

    • 比赛名单是 [("", 0), ("", 2), ("", 4), ("", 6), ("", 8)]

简单地说,第一个 x用于替换匹配的 asdf

第二个 x表示 asdf之后的空字符串。搜索结束时为空。