我偶然发现了一个令人惊讶的事实。
console.log("asdf".replace(/.*/g, "x"));
为什么要两个替换?似乎任何没有换行符的非空字符串都将为该模式产生两个替换。使用替换函数,我可以看到第一个替换是整个字符串,第二个替换为空字符串。
"aa".replace(/b*/, "b")
产生的实现细节babab
。在某些时候,我们对Web浏览器的所有实现细节进行了标准化。
我偶然发现了一个令人惊讶的事实。
console.log("asdf".replace(/.*/g, "x"));
为什么要两个替换?似乎任何没有换行符的非空字符串都将为该模式产生两个替换。使用替换函数,我可以看到第一个替换是整个字符串,第二个替换为空字符串。
"aa".replace(/b*/, "b")
产生的实现细节babab
。在某些时候,我们对Web浏览器的所有实现细节进行了标准化。
Answers:
根据ECMA-262标准,String.prototype.replace调用RegExp.prototype [@@ replace],它表示:
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
是/.*/g
和S
是'asdf'
。
见11.c.iii.2.b:
b。令nextIndex为AdvanceStringIndex(S,thisIndex,fullUnicode)。
因此,'asdf'.replace(/.*/g, 'x')
实际上是:
[]
,lastIndex =0
'asdf'
,结果= [ 'asdf' ]
,lastIndex =4
''
,结果= [ 'asdf', '' ]
,lastIndex的= 4
,AdvanceStringIndex
,lastIndex的设置为5
null
,结果= [ 'asdf', '' ]
,返回因此,有2个匹配项。
'asdf'
空字符串匹配''
。
在与yawkat进行的脱机聊天中,我们找到了一种直观的方式来了解为什么"abcd".replace(/.*/g, "x")
准确地产生了两次匹配。请注意,我们尚未检查它是否完全等于ECMAScript标准所强加的语义,因此仅以经验为准。
(matchStr, matchIndex)
按时间顺序排列的元组列表,以指示输入字符串的哪些字符串部分和索引已被消耗掉。matchIndex
覆盖该matchStr
位置的子字符串所给定的索引进行的。如果为matchStr = ""
,则“替换”实际上是插入。形式上,匹配和替换的行为被描述为一个循环,如另一个答案所示。
"abcd".replace(/.*/g, "x")
输出"xx"
:
匹配列表为 [("abcd", 0), ("", 4)]
值得注意的是,它并没有包括以下匹配一个能想到的,原因如下:
("a", 0)
,("ab", 0)
:量词*
是贪婪的("b", 1)
,("bc", 1)
::由于上一场比赛("abcd", 0)
,琴弦"b"
和"bc"
已经被吃光("", 4), ("", 4)
(即两次):索引位置4已被第一个明显的匹配所消耗因此,替换字符串"x"
将恰好在那些位置替换找到的匹配字符串:在位置0替换字符串"abcd"
,在位置4替换""
。
在这里,您可以看到替换可以真正替换以前的字符串,也可以像插入新字符串一样。
"abcd".replace(/.*?/g, "x")
带有懒惰的量词*?
输出"xaxbxcxdx"
匹配列表为 [("", 0), ("", 1), ("", 2), ("", 3), ("", 4)]
相较于先前的例子,在这里("a", 0)
,("ab", 0)
,("abc", 0)
,甚至("abcd", 0)
不包括因量词的懒惰是严格限制它来寻找可能的最短匹配。
由于所有匹配字符串均为空,因此不会发生实际替换,而是x
在位置0、1、2、3和4处插入。
"abcd".replace(/.+?/g, "x")
带有懒惰的量词+?
输出"xxxx"
[("a", 0), ("b", 1), ("c", 2), ("d", 3)]
"abcd".replace(/.{2,}?/g, "x")
带有懒惰的量词[2,}?
输出"xx"
[("ab", 0), ("cd", 2)]
"abcd".replace(/.{0}/g, "x")
输出"xaxbxcxdx"
与示例2中相同的逻辑。
我们可以始终如一地利用插入而不是替换的思想如果我们总是匹配一个空字符串并控制发生这种匹配的位置,那么。例如,我们可以创建匹配空字符串的正则表达式,在每个偶数位置在其中插入一个字符:
"abcdefgh".replace(/(?<=^(..)*)/g, "_"))
用正回顾后(?<=...)
输出"_ab_cd_ef_gh_"
(到目前为止,仅Chrome支持)
[("", 0), ("", 2), ("", 4), ("", 6), ("", 8)]
"abcdefgh".replace(/(?=(..)*$)/g, "_"))
与正超前(?=...)
输出"_ab_cd_ef_gh_"
[("", 0), ("", 2), ("", 4), ("", 6), ("", 8)]
("abcd", 0)
没有占用后面字符将到达的位置4,而零字符匹配("", 4)
却没有吃下一个角色将要到达的位置4。如果我是从头开始设计这个,我想我会使用的规则是(str2, ix2)
可以跟随(str1, ix1)
当且仅当ix2 >= ix1 + str1.length() && ix2 + str2.length() > ix1 + str1.length()
,不会引起这个缺点。
("abcd", 0)
不吃位置4,因为"abcd"
长度只有4个字符,因此只吃了索引0、1、2、3。我可以看到您的推理可能来自哪里:为什么我们不能("abcd" ⋅ ε, 0)
将5个字符长的匹配作为⋅和ε
零宽度匹配是什么?正式因为"abcd" ⋅ ε = "abcd"
。我想到了最后几分钟的直观原因,但没有找到原因。我猜一个人必须总是把它ε
当成是自己发生的""
。我很乐意尝试一个没有该错误或壮举的替代实现。随时分享!
"" ⋅ ε = ""
,尽管我不确定您打算在""
和之间划出什么区别ε
,这意味着同一件事)。因此,不能简单地将差异解释为直观的。
显然,第一个匹配项"asdf"
(位置[0,4])。由于设置了全局标志(g
),因此它将继续搜索。此时(位置4),它找到第二个匹配项,一个空字符串(位置[4,4])。
请记住,它*
匹配零个或多个元素。
"asdf".match(/.*/g)
return [“ asdf”,“”]