Make組ブログ

Python、Webアプリや製品・サービス開発についてhirokikyが書きます。

JavaScriptのグローバルマッチする正規表現でtest()、exec()すると状態が残る

今日はちょっとしたメモ書きです。

JavaScriptでグローバルマッチの(gオプションを付けた)正規表現で、 正規表現.test() をすると実行した状態が残ります。

> const FOO_REGEX = new RegExp('fo+', 'g')
> FOO_REGEX.test('fooooo is foo')
true
> FOO_REGEX.test('fooooo is foo')
true
> FOO_REGEX.test('fooooo is foo')
false

正規表現.exec() でも同様です。

> FOO_REGEX.exec('foooo is foo')
[ 'foooo', index: 0, input: 'foooo is foo', groups: undefined ]
> FOO_REGEX.exec('foooo is foo')
[ 'foo', index: 9, input: 'foooo is foo', groups: undefined ]
> FOO_REGEX.exec('foooo is foo')
null
>

グローバルマッチする正規表現.lastIndex というプロパティに、「今現在どこまで処理したか」を記録します。 .test().exec() ではその場所から次にマッチするまで処理されるので、このような挙動になります。

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

ローカル変数の場合に問題はありませんが、 グローバル変数にグローバルマッチする正規表現を置いている場合には状態が残るので注意しましょう。主に 正規表現.replace() のために使うのでグローバルマッチが良いけど、該当の文字が存在するかのチェックだけで使いたい場合などが考えられます。

対処法の考察

ここは単純にグローバルマッチをやめるのが良いと思います。

> const FOO_REGEX = new RegExp('fo+')

FOO_REGEX.lastIndex = 0 のようにすると状態を初期化できますが、グローバル変数の状態を更新するのは美しくない印象です。

「でも主に .replace() を利用したいのでグローバルマッチを付けておきたい!」ということもありますが、この場合は、うーーーん??!!! 正規表現の文字列をグローバル変数に定義しつつ、2種類の正規表現も定義するとか?ちょっと他のアイディアもあれば教えてくれると嬉しいです(すいません)。

他には 文字列.match(正規表現) を使う方法もあります。文字列全体を検証した結果が返されるので、存在のチェックだけをしたい場合に無駄な処理が多くなります。短い文字列しかない前提であれば、これでも良いかと思います。

> 'foooo is foo'.match(FOO_REGEX)
['fooo', 'foo']

まとめ

グローバルマッチする正規表現.test().exec()正規表現に状態が残るという話をしました。

執筆:Kiyohara Hiroki (@hirokiky)Shodoで執筆されました