2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JavaScript の正規表現で、エラーは出ないのに結果だけ間違う5つのパターン

2
Posted at

正規表現は、書き間違えてもエラーにならず、それらしい結果を返すことがあります。テストで使った1つ2つの文字列では正しく動いて見えるので、実際のデータで初めておかしいと気づきます。

この記事では、JavaScript の正規表現で、エラーは出ないのに結果だけ間違う5つのパターンを、Node.js v24.14.1 で実行した結果つきで紹介します。コードは .mjs ファイルに保存して node ファイル名.mjs で実行しています。

1. g を付けた正規表現を使い回すと、同じ文字列でも test が false を返す

const re = /a/g;
console.log(re.test("a"), re.test("a"), re.test("a")); // true false true

同じ文字列 "a" を3回調べただけなのに、2回目だけ false になりました。

g(全部探す)を付けた正規表現は、「前回どこまで調べたか」を lastIndex という値で覚えています。1回目でマッチすると lastIndex が 1 になり、2回目は文字列の1文字目より後ろから探すので見つからず、false を返して lastIndex が 0 に戻ります。3回目はまた先頭から探すので true です。

配列の絞り込みで使うと、結果から一部が抜けます。

const words = ["apple", "avocado", "banana", "apricot"];

const reg = /^a/g;
console.log(words.filter((w) => reg.test(w))); // [ 'apple', 'apricot' ]
console.log(words.filter((w) => /^a/.test(w))); // [ 'apple', 'avocado', 'apricot' ]

a で始まる avocado が、g 付きのほうだけ抜けました。何回目で false になるかは文字列によって変わり、"aa" を3回調べたときは true → true → false でした。test で「含まれているか」を調べるだけなら、g は付けません。

2. replace は、g を付けないと最初の1つしか置き換えない

console.log("2026-10-10".replace("-", "/"));    // 2026/10-10
console.log("2026-10-10".replace(/-/g, "/"));   // 2026/10/10
console.log("2026-10-10".replaceAll("-", "/")); // 2026/10/10

文字列を渡した replace は、最初に見つかった1か所だけを置き換えます。g を付けない正規表現(/-/)を渡しても同じで、結果は 2026/10-10 でした。全部置き換えるときは、g を付けた正規表現を渡すか、replaceAll を使います。

replaceAll に正規表現を渡すときは g が必要です。付けないと、ここだけはエラーになりました(この行はエラーで止まるので、試すときは単独で実行してください)。

"a.b.c".replaceAll(/\./, "-");
// TypeError(Node.js での表示): String.prototype.replaceAll called with a non-global RegExp argument

3. . は改行にマッチしない

const text = "開始\n本文\n終了";

console.log(/開始.*終了/.test(text));      // false
console.log(/開始.*終了/s.test(text));     // true
console.log(/開始[\s\S]*終了/.test(text)); // true

. は「どの1文字でも」という意味ですが、改行は含みません。テキストエリアに入力された複数行の文章を . で行をまたいで調べると、1行のときは動いていたのに、改行が入った途端にマッチしなくなることがあります。

改行もまたいで探すときは、s フラグを付けるか、[\s\S](空白か空白以外=どの文字でも)を使います。

4. + や . を、そのままの文字として書いている

console.log(/1+1/.test("1+1=2"));  // false
console.log(/1\+1/.test("1+1=2")); // true

+ は正規表現では「直前の文字の1回以上の繰り返し」なので、/1+1/ は「1が1回以上続いて、そのあとに1」= 11 や 111 を探します。"1+1" という文字そのものを探すなら、\+ のように \ を付けます。

. も同じで、/1.5/ は "1.5" にも "1x5" にもマッチしました。

console.log("1x5".match(/1.5/)?.[0]);  // 1x5
console.log("1x5".match(/1\.5/));      // null

利用者が入力した文字で正規表現を作るときは、記号を \ 付きに変えてから使います。記号の並び方によっては、new RegExp("C++")(Node.js での表示: SyntaxError: Invalid regular expression: /C++/: Nothing to repeat)や new RegExp("a(b")(同: Invalid regular expression: /a(b/: Unterminated group)のように、エラーで止まることもありました。Node.js v24 と、手元の WebKit(Safari と同じエンジン)では RegExp.escape が使えました。

const userInput = "a.b";
console.log(new RegExp(userInput).test("axb"));               // true(. がどの文字でもになっている)
console.log(new RegExp(RegExp.escape(userInput)).test("axb")); // false

なお、RegExp.escape("a.b") を表示すると \x61\.b になりました。先頭の英字も別の書き方に変わりますが、意味は同じ a.b という文字そのものです。

RegExp.escape は新しい機能です。MDN の互換性データでは Chrome 136、Firefox 134、Safari 18.2、Node.js 24.0.0 から使えるとされています。古い環境も対象にするなら、記号を \ 付きに置き換える関数を自分で用意します。

const escapeRegExp = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
console.log(new RegExp(escapeRegExp(userInput)).test("axb")); // false

5. \w と \d は、日本語と全角数字にマッチしない

console.log(/^\w+$/.test("tanaka_1")); // true
console.log(/^\w+$/.test("田中"));     // false
console.log("名前: 田中太郎".match(/\w+/g)); // null

console.log(/^\d+$/.test("123"));    // true
console.log(/^\d+$/.test("123")); // false

\w は半角の英字・数字・_ だけで、ひらがなや漢字、全角の英字(ABC)は含みません。\d も半角の 0〜9 だけです。「名前は文字だけ」「数字だけ」のつもりで入力チェックを書くと、日本語の名前や全角数字ではじかれます。

日本語も含めたいときは、u フラグを付けて、文字の種類で指定します。

console.log(/^\p{Script=Han}+$/u.test("田中"));    // true(漢字だけ)
console.log(/^[\p{L}\p{N}_]+$/u.test("田中_1"));   // true(文字・数字・_)
console.log(/^\p{Nd}+$/u.test("123"));          // true(全角やほかの言語の数字も含む)

\p{Nd} は、アラビア文字の数字 ٣ なども通しました。Number("٣") は NaN になるので、数値として計算するなら注意が必要です。半角と全角の数字だけを通すなら、範囲をそのまま書きます。

console.log(/^[0-90-9]+$/.test("123")); // true
console.log(/^[0-90-9]+$/.test("٣"));     // false
console.log("123".normalize("NFKC"));    // 123(全角の数字を半角にそろえる)

全角数字を通すかどうかは、入力チェックの目的によって決めます。受け取ったあとに数値として計算するなら、normalize("NFKC") で半角にそろえてから調べる方法もあります。

AI が書いた正規表現を見るときは

  • g が付いた正規表現を、変数に入れて test で何度も使っていないか
  • 文字列や g なしの正規表現を渡した replace で、全部置き換えたつもりになっていないか
  • 複数行の文章を . で調べていないか
  • + . ? などの記号を、そのままの文字のつもりで書いていないか
  • \w や \d で、日本語や全角の入力をはじいていないか

エラーで止まれば気づけますが、ここで挙げた書き方の多くは、エラーにならずに結果だけが変わります。「マッチするはずの文字列」と「マッチしてはいけない文字列」を両方用意して実際に試すと、見落としにくくなります。


未経験者向けの講座を運営しています

未経験から Next.js + Supabase + Claude Code で Webアプリを公開するまで を、全24セッションで体系化した教材です。Claude Code を学習パートナーにする CLAUDE.md と学習モード(learner / developer)の設計までセットで含みます。

※ 中上級者には易しすぎる内容なので、初心者の知り合いへの紹介や社内研修の参考としてどうぞ。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?