tl;dr: replace
的第二个参数应当使用字面量或函数,不应使用带变量的字符串表达式,否则可能发生意想不到的错误替换。
String.prototype.replace 的第二个参数
(reference: String
- JavaScript | MDN)
replace(pattern, replacement);
第二个参数 replacement
可以是字符串或函数,如果是函数,则由匹配信息作为参数计算出 replacement,如果是字符串,则可以使用一些 special replacement pattern:
Pattern | Inserts |
---|---|
$$ |
Inserts a "$" . |
$& |
Inserts the matched substring. |
$` |
Inserts the portion of the string that precedes the matched substring. |
$' |
Inserts the portion of the string that follows the matched substring. |
$n |
Inserts the n th (1-indexed) capturing group where n is a positive integer less than 100. |
$<Name> |
Inserts the named capturing group where Name is the group name. |
使用变量作为 replacement 带来的问题
我实际遇到的问题是 iles#224,由于使用了模板字符串作为 replacement,在变量中包含上面这些 special replacement pattern 时,就会错误地替换。
解决方法也很简单,将含变量的表达式改成函数(前面加上 () =>
)就可以了:regex - javascript - Better Way to Escape Dollar Signs in the String Used By String
- Stack Overflow。
因为解决的代价非常小,虽然有的时候根据代码逻辑可以推断出 replacement 不含 $
,依然可以认为,凡是 replacement 需要用到变量的,都应当替换成函数。
使用 ESLint 检测这一问题
写了个 no
的配置:
{
rules: {
'no-restricted-syntax': [
'error',
{
selector: "CallExpression[callee.property.name='replace'] > .arguments:nth-child(2):not(Literal):not(ArrowFunctionExpression):not(FunctionExpression)",
message: 'Only literals and functions are permitted as the 2nd argument of String.prototype.replace. Use a function that returns the expression instead.',
},
],
},
}
因为只是分析 AST,有很多情况会误报,例如 replacement 是一个函数名,但实际代码应该很少出现这样的情况,真遇到了的话再套一层函数就 ok 了,实在不行还能用注释 disable 掉 lint。
没研究过,不知道写 ESLint plugin 能不能更加准确地检测,但是差不多得了(