JavaScript replace() 複数置換や動的な値(変数)を使った置換

JavaScript の replace() メソッドを使って複数の文字列をまとめて置換する方法や動的な値(変数)を使った置換などについて。異なる方法による HTML 特殊文字のエスケープの例や動的な値を使った置換におけるメタ文字のエスケープなどについて掲載しています。

作成日:2024年08月17日

replace()

以下が replace() の構文です。replace() の詳細は MDN などに確認できます。

str は置換対象の文字列です。

str.replace(pattern, replacement)
引数 説明
pattern

第1引数の pattern には検索する文字列または正規表現を指定します。

文字列の場合は一度だけ置換されます。

グローバルな検索と置換を行うには、正規表現で g フラグを使用します。または、比較的新しい関数 replaceAll() を使用することもできます。

replacement

第2引数の replacement には置き換える文字列または関数を指定します。

文字列の場合、pattern に一致する文字列を第2引数に指定した文字列(replacement)で置き換えます。

関数の場合、pattern に一致するごとにその関数は呼び出され、その関数の返り値が置き換える文字列として使用されます。

返り値
最初またはすべてのマッチングで replacement によって置換された新しい文字列を返します(このメソッドは、それを呼び出した文字列を変化させません)。

以下は簡単な例です。

// 置換対象の文字列
const str = 'Hello Hello hello!';

// 第1引数に文字列、第2引数に文字列(第1引数が文字列の場合は一度だけ置換される)
const str1 = str.replace('Hello', 'Goodbye');
console.log(str1); // Goodbye Hello hello!

// 第1引数に g フラグを指定した正規表現、第2引数に文字列
const str2 = str.replace(/Hello/g, 'Goodbye');
console.log(str2); // Goodbye Goodbye hello!

// 第1引数に g と i フラグを指定した正規表現、第2引数には文字列 Goodbye を返す関数
const str3 = str.replace(/Hello/gi, () => 'Goodbye');
console.log(str3); // Goodbye Goodbye Goodbye!

// replace() は呼び出した文字列を変化させません
console.log(str); // Hello Hello hello!
正規表現のフラグ
フラグ 意味
i 大文字と小文字を区別しない。大文字小文字を無視するを意味する ignore case の i
g グローバル検索。最初に一致したものだけではなく一致するもの全てを検索します。

複数の置換

replace() を使って置換を行う際に、対象の文字列に対して複数の置換を行いたい場合があります。

複数の文字列を置換するには、以下のような方法があります。

  1. 置換対象の文字の数の分だけ replace() をチェーンして置換
  2. for 文を使って置換対象の文字の数の分だけ replace() で置換
  3. replace() の第2引数に関数を指定してまとめて置換

HTML 特殊文字のエスケープ

replace() を使った複数の置換の例として HTML 特殊文字のエスケープがあります。

HTML 特殊文字をエスケープする場合、文字列に含まれる複数の特殊文字をそれぞれ対応する HTML エンティティ(文字実体参照)に置換します。

以下はエスケープで置換する特殊文字とその文字参照(置き換える文字列)です。

特殊文字 文字実体参照
& &
< &lt;
> &gt;
' &#39;
" &quot;

replace() をチェーンして置換

以下は置換する文字の数の分だけ replace() をチェーンして置換する例です。

replace() の第1引数は正規表現で g フラグを指定して、一致するもの全てを検索するようにします。

また、置換の順番に注意が必要です。置換後のすべての文字参照には & が含まれているため、最初に & を置換する必要があります。

& を後から置換すると、2重に & が置換され期待通りの結果になりません(その他の順番は任意です)。

const html = '<p class="foo">Foo</p>';

// 最初に & を置換
const escapedHtml = html.replace(/&/g, "&amp;")
  .replace(/"/g, "&quot;")
  .replace(/'/g, "&#39;")
  .replace(/</g, "&lt;")
  .replace(/>/g, "&gt;");

console.log(escapedHtml);  // &lt;p class=&quot;foo&quot;&gt;Foo&lt;/p&gt; 

上記は以下と同じことですが、上記のようにチェーンすることで簡潔に記述できます。

const html = '<p class="foo">Foo</p>';

let escapedHtml = html.replace(/&/g, "&amp;"); // 最初に & を置換
escapedHtml = escapedHtml.replace(/"/g, "&quot;");
escapedHtml = escapedHtml.replace(/'/g, "&#39;");
escapedHtml = escapedHtml.replace(/</g, "&lt;");
escapedHtml = escapedHtml.replace(/>/g, "&gt;");

for 文を使って置換

以下は置換対象の文字の数の分だけ for 文を使って replace() で繰り返し置換する例です。

特殊文字の配列とその各要素に対応する文字実体参照の配列を用意します。

この場合も、最初に & を置換するように、配列の先頭に & と対応する &amp; をそれぞれ配置します。

for 文の中では、特殊文字の配列の要素から正規表現パターンを生成して replace() の第1引数に指定しますが、正規表現パターンの生成では new 演算子とコンストラクタ関数 RegExp() を使います。

その際、フラグは第2引数に指定します。

リテラル記法を使って /specialChars[i]/g のように指定することはできません。

// で囲まれたパターン文字列に変数を使いたい(動的に正規表現を生成する)場合は RegExp オブジェクトのコンストラクタを使って正規表現オブジェクトを生成します。

const html = '<p class="foo">Foo</p>';

// 特殊文字の配列 (先頭に &)
const specialChars = ["&", "<", ">" ,'"', "'"];
// 文字実体参照の配列 (先頭に &amp;)
const entityRefs = ["&amp;", "&lt;", "&gt;", "&quot;", "&#39;"];

// 置換後の結果を入れる変数(最初は置換対象の文字列を代入)
let escapedHtml = html;

for(let i=0; i<specialChars.length; i++){
  // 特殊文字の配列の要素から正規表現パターンを作成して replace() の第1引数に指定し、第2引数には対応する文字実体参照の配列の要素を指定
  escapedHtml = escapedHtml.replace(new RegExp(specialChars[i], 'g'), entityRefs[i]);
}

console.log(escapedHtml);  // &lt;p class=&quot;foo&quot;&gt;Foo&lt;/p&gt;

replace() の第2引数に関数を指定

以下は replace() の第2引数に関数を指定してまとめて置換する例です。

第2引数に関数を指定した場合、第1引数に指定した pattern に一致するごとにその関数は呼び出され、その関数の返り値が置き換える文字列として使用されます。

第1引数には文字クラス [] を使って /[<>&'"]/g のように指定しているので、< > & ' " のいずれかにマッチするたびに第2引数の関数が呼び出されます。

引数の match には一致した部分文字列が渡されます。

関数の中ではキーが特殊文字、値が対応する文字実体参照のプロパティを持つオブジェクト escapeMap を定義しています。この場合、定義する順番は任意で、& を最初に定義する必要はありません。

オブジェクトのプロパティ名(キー)に変数を使用する場合は、プロパティ名を角括弧 [] で囲んでアクセスします。ドット演算子(.)ではアクセスできません。

例えば、'<' にマッチすると引数 match には '<' が渡されるので、return escapeMap[match]escapeMap['<']、つまり '&lt;' を返すことで置換されます。

const html = '<p class="foo">Foo</p>';

// 第1引数に < > & ' " のいずれにもマッチするように文字クラス [] を使い、g フラグを指定
const escapedHtml = html.replace(/[<>&'"]/g, (match) => {
  // キーが特殊文字で値が文字実体参照のプロパティを持つオブジェクトを定義
  const escapeMap = {
    '<': '&lt;',
    '>': '&gt;',
    '&': '&amp;',
    "'": '&#39;',
    '"': '&quot;'
  };
  // オブジェクトのキーは変数なので、値にアクセスするには角括弧 [ ] を使用。ドット演算子(.)ではアクセスできない。
  return escapeMap[match];
});

console.log(escapedHtml); // &lt;p class=&quot;foo&quot;&gt;Foo&lt;/p&gt;

どの方法が良いのか?

replace() の第2引数に関数を指定する方法が僅かに効率的かもしれませんが大きな差はないと思われます。

関連ページ:HTML特殊文字変換

動的な値で置換

前述の HTML 特殊文字のエスケープの置換はあらかじめ決まった文字列を決まった値で置換する例でしたが、以下は動的な(あらかじめ決まっていない)値で置換する例です。

この例では、カスタムデータ(data-*)属性に指定された文字列の色を指定された色に変更します。具体的には replace() を使って指定された文字列を span 要素で囲みインラインスタイルで色を設定します。

例えば、以下のような HTML を記述します。対象の要素には highlight-text クラスを指定し、色を変更する文字列を data-target-text 属性に、その文字色を data-text-color 属性に指定します。

<p class="highlight-text" data-target-text="amet" data-text-color="red">Lorem ipsum dolor sit amet consectetur, adipisicing elit amet. </p>

変数や動的な値を使った正規表現パターンの生成ではリテラル記法は使えないので、new 演算子とコンストラクタ関数 RegExp() を使います。

そのため、data-target-text 属性に指定された文字列から replace() の第1引数の正規表現パターンを生成する際には new RegExp() を使います。

そして第2引数の置換文字列には、指定された文字列($&)を span 要素で囲みインラインスタイルで指定された文字色を設定します。

$& は置換文字列に使用できる特殊な置換パターンの1つで、一致した部分文字列(上記の例の場合は data-target-text に指定された amet)を挿入します。$& は第2引数に関数を指定した場合の match に対応します。

// 対象の要素を取得
const target = document.querySelector('.highlight-text');

// 対象の要素が存在すれば
if (target) {
  // その要素のカスタムデータ属性(dataset プロパティ)を取得
  const targetData = target.dataset;
  // data-target-text 属性の値(前後の空白を除去)
  const targetTextValue = targetData.targetText.trim();
  // data-text-color 属性の値(前後の空白を除去)
  const colorToSet = targetData.textColor.trim();
  // 上記の属性に値が設定されていれば
  if (targetTextValue && colorToSet) {
    // 指定された文字列を指定された色で表示
    target.innerHTML = target.innerHTML.replace(
      new RegExp(targetTextValue, 'g'),  // 第1引数
      `<span style="color:${colorToSet};">$&</span>`  // 第2引数
    );
  }
}

上記は、例えば以下のように表示されます。

Lorem ipsum dolor sit amet consectetur, adipisicing elit amet.

以下が出力される置換後のマークアップです。

<p class="highlight-text1" data-target-text="amet" data-text-color="red">Lorem ipsum dolor sit <span style="color:red;">amet</span> consectetur, adipisicing elit <span style="color:red;">amet</span>. </p>

動的な値のパターン(メタ文字のエスケープ)

上記の例の場合、実際には data-target-text 属性に指定する文字列に制限があります。

data-target-text 属性に指定された値を使って replace() の第1引数に指定する正規表現パターンを作成していますが、正規表現の中では特別な意味を持つ文字(メタ文字)があります。

置換対象に指定した文字列が半角英数字や全角文字だけの場合は問題ありませんが、以下のメタ文字が含まれていると期待通りの結果になりません。

^ $ . * + ? = ! : | \ / ( ) [ ] { }

メタ文字を正規表現パターンの中に含めると、期待通りに置換されなかったり SyntaxError: Invalid regular expression エラーが発生する可能性があります(メタ文字の位置にもよりますが)。

対策としては、data-target-text 属性にメタ文字を含める場合は適切にエスケープするか、data-target-text 属性に指定された文字列にメタ文字が含まれている場合はエスケープするなどが考えられます。

以下は data-target-text 属性に指定された値にメタ文字が含まれている場合はエスケープする例です。

7〜9行目で replace() の第1引数にメタ文字から成る文字クラス /[\^\$\*\+\?\=\!\:\|\\\/\(\)\[\]\{\}]/g を指定して、それらの文字が含まれていれば \\ を追加してエスケープしています。

const target = document.querySelector('.highlight-text');

if (target) {
  const targetData = target.dataset;

  // data-target-text 属性の値にメタ文字が含まれていればメタ文字の前に \\ を追加してエスケープ
  const targetTextValue = targetData.targetText.trim().replace(/[\^\$\*\+\?\=\!\:\|\\\/\(\)\[\]\{\}]/g, (match) => {
    return '\\' + match;
  });

  const colorToSet = targetData.textColor.trim();
  if (targetTextValue && colorToSet) {
    target.innerHTML = target.innerHTML.replace(
      new RegExp(targetTextValue, 'g'),
      // 第2引数を関数にする場合の例(関数にする理由は特にありませんが)
      (match) => `<span style="color:${colorToSet};">${match}</span>`
    );
  }
}

複数置換の例

前述の例では、1つの置換対象の文字列しか指定できませんでしたが、以下は複数指定できる例です。

対象の要素には highlight-texts クラスを指定し、data-target-texts 属性に色を変更する文字列をカンマ区切りで指定し、data-text-colors 属性にその背景色をカンマ区切りで指定します。

<p class="highlight-texts" data-target-texts="ipsum, sit, elit" data-text-colors="#499455, tomato, #313d98">Lorem ipsum dolor sit amet consectetur, adipisicing elit amet. </p>

以下がコードです。

HTML 特殊文字のエスケープの場合と同様、対象の文字列をキー、その色を値とするプロパティを持つオブジェクトを作成して、replace() の第2引数に関数を使って置換します。

動的な値を使って replace() の第1引数の正規表現パターンを作成するため、メタ文字が含まれている場合は \\ を追加してエスケープしています(22-24行目)。

但し、オブジェクトのキーに指定する際には、エスケープ文字を除去しています(35-38行目)。これは replace() を呼び出しで第2引数の関数に渡される引数 match にはエスケープ文字が含まれないためです。

35行目の replace() の第2引数の関数の引数 p1(.) にマッチしたメタ文字が入っています。

data-text-colors 属性に色を1つだけ指定した場合は、すべて最初の色で表示します(41行目)。

対象の文字列は、背景色をインラインスタイルで設定した span 要素で囲みます。その際に span 要素に highlight-target クラスを指定して別途 CSS でスタイルを設定できるようにしています。

// 対象の要素を取得
const target = document.querySelector('.highlight-texts');

// 対象の要素が存在すれば
if (target) {
  // その要素のカスタムデータ属性(dataset プロパティ)を取得
  const targetData = target.dataset;

  // data-target-text 属性 と data-text-color 属性の値が空でなければ
  if (targetData.targetTexts && targetData.textColors) {
    // 値をカンマで分割(targetTextValues と colorToSets は配列)
    const targetTextValues = targetData.targetTexts.split(',');
    const colorToSets = targetData.textColors.split(',');

    // replace() の第1引数に指定する正規表現パターンに使用する文字列の初期化
    let targetTextsRegExValue = '';
    // キーに対象の文字列(data-target-text)、値に色の文字列(data-text-color)のプロパティを持つオブジェクトの初期化
    let colorMap = {};

    for (let i = 0; i < targetTextValues.length; i++) {
      // 対象の文字列にメタ文字が含まれていれば \\ をメタ文字の前に追加してエスケープ
      const targetText = targetTextValues[i].trim().replace(/[\^\$\*\+\?\=\!\:\|\\\/\(\)\[\]\{\}]/g, (match) => {
        return '\\' + match;
      });

      // replace() の第1引数に指定する正規表現パターンに使用する文字列を作成
      if (targetText) {
        if (i === 0) {
          targetTextsRegExValue = targetText;
        } else {
          targetTextsRegExValue = targetTextsRegExValue + '|' + targetText;
        }

        // colorMap オブジェクトのプロパティのキー(メタ文字をエスケープするために追加した \\ を除去)
        const keyVal = targetText.replace(/\\(.)/g, (match, p1) => {
          // エスケープ文字を除去してメタ文字のみに置換(返す)
          return p1;
        });

        // colorMap オブジェクトにプロパティを追加(キー:対象の文字列、値:色)
        colorMap[keyVal] = colorToSets[i] ? colorToSets[i].trim() : colorToSets[0].trim();
      }
    }

    // replace() の第1引数に指定する正規表現パターンを作成
    const targetTextsRegEx = new RegExp(targetTextsRegExValue, 'g');

    // 指定された文字列を指定された背景色で表示
    target.innerHTML = target.innerHTML.replace(targetTextsRegEx, (match) => {
      // マッチした文字列にインラインスタイルで対応する背景色を設定
      return `<span class="highlight-target" style="background-color: ${colorMap[match]}">${match}</span>`;
    });
  }
}

上記は、例えば以下のように表示されます。

Lorem ipsum dolor sit amet consectetur, adipisicing elit amet.

別途 CSS で span 要素に付与された highlight-target クラスを使って以下のスタイルを追加しています。

.highlight-target {
  padding: 3px;
  color: #fff;
}