String.prototype.replaceAll

String.prototype.replaceAll

如果你曾经在 JavaScript 中处理过字符串的话,你一定考虑过使用 String#replace 方法。String.prototype.replace(searchValue, replacement) 返回一个替换满足条件的字符串,基于声明的参数:

'abc'.replace('b', '_');
// → 'a_c'

'🍏🍋🍊🍓'.replace('🍏', '🥭');
// → '🥭🍋🍊🍓'

一个常见的用例是替换一个给定子串的所有实例。然而,String#replace 并没有直接解决这个用例。当 searchValue 是一个字符串时,只有子串的第一次出现会被替换:

'aabbcc'.replace('b', '_');
// → 'aa_bcc'

'🍏🍏🍋🍋🍊🍊🍓🍓'.replace('🍏', '🥭');
// → '🥭🍏🍋🍋🍊🍊🍓🍓'

为了使其工作达到目的,开发者通常需要转换搜索字符串为带全局(g)标志的正则表达式。使用这种方法,String#replace 将替换所有的满足条件:

'aabbcc'.replace(/b/g, '_');
// → 'aa__cc'

'🍏🍏🍋🍋🍊🍊🍓🍓'.replace(/🍏/g, '🥭');
// → '🥭🥭🍋🍋🍊🍊🍓🍓'

作为一个开发者,如果你真正想要的是全局子串替换,那么不得不进行这种字符串到 regexp 的转换是很烦人的。更重要的是,这种转换很容易出错,也是常见的 bug 来源! 考虑一下下面的例子:

const queryString = 'q=query+string+parameters';

queryString.replace('+', ' ');
// → 'q=query string+parameters' ❌
// Only the first occurrence gets replaced.

queryString.replace(/+/, ' ');
// → SyntaxError: invalid regular expression ❌
// As it turns out, `+` is a special character within regexp patterns.

queryString.replace(/\+/, ' ');
// → 'q=query string+parameters' ❌
// Escaping special regexp characters makes the regexp valid, but
// this still only replaces the first occurrence of `+` in the string.

queryString.replace(/\+/g, ' ');
// → 'q=query string parameters' ✅
// Escaping special regexp characters AND using the `g` flag makes it work.

将像 '+' 这样的字符串文字转化为全局正则表达式,不仅仅是去掉 ' 引号,将其包装成 / 斜线,并附加 g 标志的问题 -- 我们必须转义任何在正则表达式中具有特殊意义的字符。这一点很容易忘记,也很难做到正确,因为 JavaScript 并没有提供一个内置的机制来转义正则表达式。

一个可替换的变通方法是组合使用 String#splitArray#join

const queryString = 'q=query+string+parameters';
queryString.split('+').join(' ');
// → 'q=query string parameters'

这种方法避免了任何问题的情况,但却带来了将字符串分割成一个数组,然后再将其粘合在一起的开销。

显然,这些变通方法都不理想。如果像全局子串替换这样的基本操作能在 JavaScript 中直接实现,那岂不更好?

String.prototype.replaceAll

新的 String#replaceAll 方法解决了这些问题,并且提供一个简单的机制来全局替换子串:

'aabbcc'.replaceAll('b', '_');
// → 'aa__cc'

'🍏🍏🍋🍋🍊🍊🍓🍓'.replaceAll('🍏', '🥭');
// → '🥭🥭🍋🍋🍊🍊🍓🍓'

const queryString = 'q=query+string+parameters';
queryString.replaceAll('+', ' ');
// → 'q=query string parameters'

为了与语言中已存在的 API 一致,String.prototype.replaceAll(searchValue, replacement) 的行为确实类似 String.prototype.replace(searchValue, replacement),却有下面两个区别:

  1. 如果 searchValue 是一个字符串,String#replace 仅仅替换首个匹配的子串,而 String#replaceAll 替换所有的匹配项。
  2. 如果 searchValue 是一个非全局正则表达式,String#replace 仅替换单个匹配,类似于字符串的行为。String#replaceAll 在这种用例下抛出一个异常,因为这可能是个错误:如果你真的想“替换所有”匹配,你会使用全局正则表达式;如果你只想替换单个匹配,你可以使用 String#replace

重要的新功能就在于第一项。String.prototype.replaceAll 丰富了 JavaScript,为全局子串替换提供了一流的支持,而无需使用正则表达式或其他变通方法。

关于标准替换模式的说明

值得一提的是:replacereplaceAll 都支持标准的替换模式。尽管这些模式在与正则表达式结合时最有用,但其中一些模式($$$&$`` 和 $'`)在进行简单的字符串替换时也会生效,这可能会令人惊讶:

'xyz'.replaceAll('y', '$$');
// → 'x$z' (not 'x$$z')

如果你的替换字符串包含这些模式之一,并且你想按原样使用它们,你可以通过使用一个返回字符串的替换函数来选择不使用神奇的替换行为:

'xyz'.replaceAll('y', () => '$$');
// → 'x$$z

String.prototype.replaceAll 支持

本文作者 Mathias Bynens,转载请注明来源链接:

原文链接:https://v8.dev/features/string-replaceall

本文链接:https://tie.pub/2020/06/string-replaceall/