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,为全局子串替换提供了一流的支持,而无需使用正则表达式或其他变通方法。

String.prototype.replaceAll 支持