正規表現を複数回実行する際の注意点

  • 66
    いいね
  • 1
    コメント

javascriptで正規表現を複数回実行する際の注意点

以下のコードは"hoge"という文字列が含まれているか正規表現で検索し、結果を表示するコードです。

hoge.js
var example = "hoge";
var regexp = /hoge/g;

regexp.test(example);
// true
regexp.test(example);
// false
regexp.test(example);
// true
regexp.test(example);
// false
...

一度目の実行ではtrueが返ってきますが、二度目の実行ではfalseが返ってきます。
以降は交互に結果が変わります。不思議ですね。

私はループ内で正規表現を実行する際に、この挙動につまづきました。
一体何が起きているのでしょうか?

hoge_loop.js
var examples = ["hoge", "hoge", "hoge", "hoge", "hoge", "hoge"];
var regexp = /hoge/g;
var result = [];

examples.forEach(function(example) {
  result.push(regexp.exec(example));
})

console.log(result);
// [['hoge'], null, ['hoge'], null, ['hoge'], null]

原因

正規表現に/gフラグが付与されている場合、最後に一致した文字列がregexp.lastIndexに保持されます。
regexp.lastIndexと同一の文字列を引数にした場合、その文字列から先を対象に正規表現が検証されます。
先程の例の場合、"hoge"は前回ヒットした内容と同一文字列のためスキップされてしまいました。

解決策1

正規表現ごと初期化することで問題を解決できます。

hoge_answer1.js
var examples = ["hoge", "hoge", "hoge", "hoge", "hoge", "hoge"];
var result = [];

examples.forEach(function(example) {
  var regexp = /hoge/g; //ループごとに初期化
  result.push(regexp.exec(example));
})

console.log(result);
// [['hoge'], ['hoge'], ['hoge'], ['hoge'], ['hoge'], ['hoge']]

解決策2

regexp.lastIndexを初期化することで問題を解決できます。

hoge_answer2.js
var examples = ["hoge", "hoge", "hoge", "hoge", "hoge", "hoge"];
var regexp = /hoge/g;
var result = [];

examples.forEach(function(example) {
  result.push(regexp.exec(example));
  regexp.lastIndex = 0; // lastIndexの初期化
});

console.log(result);
// [['hoge'], ['hoge'], ['hoge'], ['hoge'], ['hoge'], ['hoge']]

解決策3

そもそも必要ないのであれば、正規表現に/gフラグを付与しなければ問題を解決できます。

hoge_answer3.js
var examples = ["hoge", "hoge", "hoge", "hoge", "hoge", "hoge"];
var regexp = /hoge/;
var result = [];

examples.forEach(function(example) {
  result.push(regexp.exec(example));
})

console.log(result);
// [['hoge'], ['hoge'], ['hoge'], ['hoge'], ['hoge'], ['hoge']]

解決策4

regexp.exec(str)ではなくstring.match(regexp)を利用することで問題を解決できます。
(※それぞれの/gフラグが付与されている場合の挙動の違いについては注意してください。)

hoge_answer4.js
var examples = ["hoge", "hoge", "hoge", "hoge", "hoge", "hoge"];
var regexp = /hoge/g;
var result = [];

examples.forEach(function(example) {
  result.push(example.match(regexp));
})

console.log(result);
// [['hoge'], ['hoge'], ['hoge'], ['hoge'], ['hoge'], ['hoge']]

以上、連続して同一文字列を検証する可能性がある場合はお気をつけください!
(コメントにてご指摘を頂きました皆様ありがとうございます!)