# RegExp.testが交互にtrue・falseになる理由｜gフラグとlastIndex

同じ文字列なのに正規表現のtestが通ったり落ちたりするのはなぜ？g・yフラグが保持する検索位置と、入力チェックでの直し方を、繰り返し実行できるサンプルで確認します。

正規URL: https://3mikan.com/archives/6088

著者: みかん
公開: 2026-09-08T15:06:00.000Z
更新: 2026-09-08T15:06:00.000Z

数字だけの文字列を確認しているのに、同じ`"42"`が通ったり落ちたりする。正規表現の使い回しで起きるこの症状は、パターン末尾の`g`フラグが原因かもしれません。

`g`や`y`を付けた正規表現は、**次に調べる位置を`lastIndex`に保持します**。単に入力が条件に合うかを毎回独立して確認したいなら、不要な`g`・`y`を外すのが分かりやすい修正です。

## まずは手元で再現する

[実行用サンプルを開く・保存する](/examples/regexp-last-index.mjs)から、`regexp-last-index.mjs`という名前で保存して実行してください。

```sh
node regexp-last-index.mjs
```

確認環境はNode.js v24.14.0です。追加パッケージ、アカウント、ネットワーク通信、ファイルの書き込みは不要です。最後に「すべての実行例が期待値と一致しました。」と出れば、繰り返し判定、修正後の判定、空の一致まで確認できています。以下のJavaScriptの枠も、それぞれ別の`.mjs`ファイルで実行できます。

## 同じ文字列でも、開始位置が変わっている

```js
const pattern = /^[0-9]+$/g;
for (const input of ['42', '42', '42']) {
  console.log(pattern.test(input), pattern.lastIndex);
}
// true 2
// false 0
// true 2
```

このパターンは、先頭から末尾までが半角数字であることを調べます。文字列は変えていませんが、正規表現オブジェクトの状態が変わります。

| 呼び出し | 開始時の位置 | 結果と次の位置 |
| --- | --- | --- |
| 1回目 | 0 | 一致して`true`、位置は2 |
| 2回目 | 2 | 一致せず`false`、位置は0 |
| 3回目 | 0 | 一致して`true`、位置は2 |

`g`付きでは、成功すると`lastIndex`が一致した末尾へ移ります。次の呼び出しはその位置から探すため、2回目は先頭に一致するこのパターンを満たせません。失敗すると位置が0に戻り、3回目は再び通ります。[ECMAScriptの正規表現実行規則](https://tc39.es/ecma262/2025/multipage/text-processing.html#sec-regexpbuiltinexec)

入力を別の文字列に変えても、自動で位置が初期化されるわけではありません。実行用サンプルでは、同じ正規表現で`"12"`を調べた直後に`"34"`を調べると、後者が`false`になることも確認しています。[MDNのtest解説](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test#using_test_on_a_regex_with_the_global_flag)

## 入力チェックなら、不要なgとyを外す

半角数字だけの文字列かどうかを判定するなら、次のように書けます。

```js
const digits = /^[0-9]+$/;
const isDigits = (value) => typeof value === 'string' && digits.test(value);

console.log(['42', '42', '42'].map(isDigits).join(',')); // true,true,true
console.log(isDigits('4x'));  // false
console.log(isDigits('４２')); // false
console.log(isDigits(''));    // false
console.log(isDigits(42));    // false
console.log(digits.test(42)); // true
```

ここでは「半角数字が1文字以上ある文字列」を受け付け、数値の`42`は受け付けない方針にしています。`test()`は引数を文字列に変換するため、型も限定したい場合は先に`typeof`で確認します。型の確認と正規表現の確認は別の役割です。[RegExp.testの引数](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test)

実行用サンプルでは、末尾に改行のある`"42\n"`や、`null`、`undefined`も拒否することを照合しています。これは上のパターンと条件での結果であり、フラグやパターンを変えた場合の入力ルールまで保証するものではありません。

既存の処理で`g`付きの正規表現を使う必要があるなら、各入力を調べる**前**に`pattern.lastIndex = 0`と戻す方法もあります。ただし、その後の`test()`でまた状態は変わります。入力チェックだけなら、位置を持たせない方が扱いやすくなります。

## yへ変えれば直る、というわけではない

`y`も`lastIndex`を使うフラグです。違いは、`g`が後ろの位置も探すのに対し、`y`は指定位置から一致する必要があることです。

```js
const global = /[0-9]/g;
const sticky = /[0-9]/y;
console.log(global.test('x7'), global.lastIndex); // true 2
console.log(sticky.test('x7'), sticky.lastIndex); // false 0
sticky.lastIndex = 1;
console.log(sticky.test('x7'), sticky.lastIndex); // true 2
```

位置0には`x`があるので、`y`では一致しません。位置1を指定すれば`7`に一致します。これは文字列を位置に沿って読み進める用途で意味のある違いです。毎回独立した入力判定をしたい場面で、`g`を`y`へ置き換えるだけでは解決しません。[RegExpBuiltinExecのglobalとstickyの分岐](https://tc39.es/ecma262/2025/multipage/text-processing.html#sec-regexpbuiltinexec)

## trueでも位置が進まない例がある

正確には、成功したときに位置が「必ず増える」のではなく、「一致した末尾になる」という規則です。文字を消費しない一致なら、位置は増えません。

```js
const pattern = /^/g;
console.log(pattern.test('42'), pattern.lastIndex); // true 0
console.log(pattern.test('42'), pattern.lastIndex); // true 0
```

このパターンは先頭という位置に一致するため、末尾も0のままです。`while (pattern.test(text))`のような書き方をするとき、`true`なら自動で前進すると考えるのは危険です。

まず、入力が条件を満たすかを確認したいのか、一致箇所を順番に読みたいのかを分けましょう。前者なら不要な`g`・`y`を外す。後者なら開始位置と、空の一致をどう扱うかまで決める。この区別が、繰り返したときだけ結果が変わる不具合を直す手掛かりになります。

## 確認した一次情報

- [ECMAScript 2025: RegExpBuiltinExecとlastIndex](<https://tc39.es/ecma262/2025/multipage/text-processing.html#sec-regexpbuiltinexec>): 確認日 2026-09-08
- [MDN: RegExp.prototype.test](<https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test>): 確認日 2026-09-08
