🍣

Javascriptの正規表現 グローバルフラグでtestの結果が変わる話

2022/07/12に公開

Javascriptの正規表現のグローバルフラグ g によって、正規表現のtestを複数回実行した場合に予期しない結果を返すことがあります。

事例

バージョン番号が正しいかをチェックする正規表現を例に説明します。
次のコードのように文字列がバージョン番号であるかどうかを示す正規表現を作成して、test で文字列が正規表現にマッチングするかチェックします。
このときに、test を複数回呼び出すと true となるはずの結果が false となることがあります。

const versionPatternG = /^\d+\.\d+\.\d+\.\d+$/g;
versionPatternG.test('2.0.6.121'); // => true
versionPatternG.test('2.0.6.122'); // => false

原因は /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/g の グローバルフラグ g が原因でした。
今回のケースでは g は使用する必要がなかったので取り除いて実行したところ、想定通りの結果を返しました。

const versionPattern = /^\d+\.\d+\.\d+\.\d+$/;
versionPattern.test('2.0.6.121'); // => true
versionPattern.test('2.0.6.122'); // => true

グローバルフラグを持つ正規表現の test() の使用

グローバルフラグ g を付けることで正規表現が持つ lastIndex が加算されることが原因でした。
lastIndex が加算されて、次に指定した文字列でも lastIndex の位置から探索を行うため、test で想定しているマッチングができなくなりました。
そして、test の結果が false になると、lastIndex が 0 にリセットされます。

正規表現にグローバルフラグが設定されている場合、 test() は正規表現が所有する lastIndex の値を加算します。 (exec() も同様に lastIndex プロパティの値を加算します。)

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test#using_test_on_a_regex_with_the_global_flag

Discussion