String.prototype.replaceAll

发布日期 · 标签:ECMAScript ES2021

如果您曾经在 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, '🥭');
// → '🥭🥭🍋🍋🍊🍊🍓🍓'

作为一名开发人员,如果只想进行全局子字符串替换,就必须进行这种字符串到正则表达式的转换,这很烦人。更重要的是,这种转换容易出错,也是常见错误的来源!请考虑以下示例。

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 支持 #