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)は、以下の2つの例外を除いて、String.prototype.replace(searchValue, replacement)とまったく同じ動作をします。

  1. searchValueが文字列の場合、String#replaceはサブストリングの最初の出現箇所のみを置き換えますが、String#replaceAllすべての出現箇所を置き換えます。
  2. searchValueがグローバルではないRegExpの場合、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のサポート #