Panda Noir

JavaScript の限界を究めるブログでした。最近は幅広めに書いてます。

JavaScriptの正規表現に変数を組み込む方法

正規表現の中に変数を組み込むことにあれこれと悩んでる人はお読みください。

RegExpなら埋め込める

RegExpというオブジェクトをご存知でしょうか? RegExpとは、正規表現オブジェクトを生成するコンストラクタです。

正規表現オブジェクトは new RegExp(パターン, 修飾子) で生成できます。new RegExp(パターン, 修飾子) は /パターン/修飾子 という正規表現リテラルと等価です(厳密には違う)。

new RegExp("hogefuga", "gim") == /hogefuga/gim // ほんとはちょっと違う

RegExpの場合、パターンとして文字列を渡します。それに対し、正規表現リテラルは変数を組み込む部分がありません。

実際に正規表現に変数を組み込むと、こういった感じになります。

let result = str; // なんらかの文字列
for (const word of words) {
    const reg = new RegExp(`${word}\n`, 'g');
    result = result.replace(reg, '');
}

`${word}\n`にマッチしたものを result から削除できます。

補足: リテラルとnew RegExpのちがい

両者はほぼ問題にならないくらい、同じです。

const literal = /hoegfuga/;
const obj = new RegExp('hogefuga', '');

typeof literal === typeof obj; // 'object' === 'object'
({}).toString.call(literal) === ({}).toString.call(obj); // '[object RegExp]' === '[object RegExp]'

str.replace(literal, ''); // こっちでも
str.replace(obj, ''); // こっちでもいける

literal.test(str); // これも
obj.test(str); // どっちでもいい

ただし、エスケープするときの手間が違います。objのほうはilteralの2倍の手間がかかります。

const literal = /\\n/;
const obj = new RegExp('\\\\n', '');
// ちなみにこうすればエスケープの手間ははぶける
// const obj = new RegExp(String.raw`\\n`, '');

また、メソッドを呼び出す時のパフォーマンスも落ちます。

console.time('literal');
for(let i = 0; i < 1e5; i++) literal.test('hoge');
console.timeEnd('literal');

console.time('obj');
for(let i = 0; i < 1e5; i++) obj.test('hoge');
console.timeEnd('obj');

Node v10.11.0、Ubuntu 18.04で検証したところ、1.5倍ちかくobjの方が早い結果になりました。

literal: 4.057ms
obj: 2.930ms

終わりに

あまりRegExpオブジェクトを使う場面はありませんが、こういう使い方ができるよ、という記事でした(というかコレ以外の場面はだいたい正規表現リテラルで良い気がする)。