
正規表現の先読み・後読み完全ガイド — (?=...) と (?<=...) を実務で使い分ける
正規表現の先読み(lookahead)と後読み(lookbehind)は、現在位置の前後が条件に一致するかを検査しても、その検査部分の文字をマッチ全体へ含めないzero-width assertionです。「この文字の直後に〜があるとき」「この文字の直前に〜があるとき」といった条件を書けます。なお、lookaround内部に通常の括弧を置けばキャプチャは作られるため、不要なら (?:...) を使います。本記事では4種類のlookaroundを実務パターン中心に整理します。
4種類の lookaround 構文
表が収まらない場合は、左右にスクロールできます。
| 構文 | 名称 | 意味 |
|---|---|---|
(?=X) | 肯定先読み | 直後にXがある |
(?!X) | 否定先読み | 直後にXがない |
(?<=X) | 肯定後読み | 直前にXがある |
(?<!X) | 否定後読み | 直前にXがない |
重要な特徴: lookaround は文字を"消費"しません。マッチ位置はチェック前のまま進みません。そのため、連続したlookaroundを並べてAND条件のように使えます。
パターン1: 特定の文字の前だけ抜き出す
「価格の数字だけ取り出したい。後ろに"円"が付いているもの限定」というケース。
// "商品A 500円, 送料 300円, コード 1234" から円の前の数字だけ
const text = "商品A 500円, 送料 300円, コード 1234";
text.match(/\d+(?=円)/g);
// ["500", "300"] — "1234" はヒットしない
キャプチャグループ (\d+)円 で 円 までマッチさせてから [1] で抜き出すのは定番ですが、lookahead なら match 結果がそのまま数字だけになります。
パターン2: 特定の文字の後だけ抜き出す
「@ の後のドメインだけ」「$ の後の金額だけ」といったケースに後読みが便利です。
// メールアドレスからドメインだけ抽出
"alice@example.com, bob@nantoo.jp".match(/(?<=@)[\w.-]+/g);
// ["example.com", "nantoo.jp"]
// $ の後の金額
"price $99.99, tax $9.50".match(/(?<=\$)\d+(?:\.\d+)?/g);
// ["99.99", "9.50"]
これらは形式が既知の文字列から一部を抜き出す例であり、完全なメールアドレス検証や通貨額検証ではありません。入力検証では、許可する形式を別途定義してください。
パターン3: 特定の条件を避けるため否定 lookahead
「特定の文字が直後に続く候補を除外する」といった条件は、否定lookaroundで書けます。
// 数字の塊のうち、直後にワード文字(\w = [A-Za-z0-9_])が続かないものだけ
"abc123xyz 789".match(/\d+(?!\w)/g);
// ["789"]
// 解説:
// - "123" は直後に 'x' (\w) があるため、\d+ をどう短縮しても (?!\w) が成立せず除外
// - "789" は直後が文字列末尾(文字なし=ワード文字でない)のためマッチ
同じ結果を「直後が非ワード文字または文字列末尾」と肯定形で書くこともできます。書き手の意図がより伝わる方を選んでください。
"abc123xyz789!end 456".match(/\d+(?=[^\w]|$)/g);
// ["789", "456"]
// "123" は直後 'x' で除外、"789" は直後 '!'、"456" は文字列末尾でマッチ
否定 lookahead は直感と逆の挙動になりがちです。正規表現テスターで条件をひとつずつ検証するのが安全です。
パターン4: 複数条件のAND結合
複数の条件を同時に確認する構文例として、ASCIIの数字と英字だけを許可し、その両方が1文字以上含まれ、全体が8文字以上かを判定します。パスワード例でよく紹介されますが、NIST SP 800-63Bは文字種を混在させるcomposition ruleをパスワード要件として課さないよう定めています。ここではlookaheadの構文説明だけを目的とします。
// 8文字以上・数字と英字を両方含む
const passwordRe = /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$/;
passwordRe.test("abc12345"); // true
passwordRe.test("abcdefgh"); // false (数字なし)
passwordRe.test("12345678"); // false (英字なし)
passwordRe.test("abc123"); // false (短い)
(?=...) は位置を進めずに条件だけチェックするため、連続して並べることでAND条件を組めます。3つ以上の条件でも同じパターンで拡張可能です。
ただし、この例が確認するのは文字種と長さだけです。パスワードの強度や漏えい済み文字列かどうかを判定するものではありません。実際の認証では十分な最大長を許可し、レート制限、多要素認証、漏えいパスワード照合などを別に設計します。
パターン5: 区切り文字で分割せず置換したい
3桁ごとにカンマを入れる「1234567 → 1,234,567」という処理は、JavaScriptでは toLocaleString で済みますが、lookaround を使えば正規表現1行で書けます。
"1234567890".replace(/\B(?=(?:\d{3})+(?!\d))/g, ",");
// "1,234,567,890"
解説: \B は単語境界以外、(?=(?:\d{3})+(?!\d)) は「この位置以降に "3桁の倍数 + 数字でない境界" が続く」位置を見つけます。内部の (?:...) はキャプチャを作らないグループです。つまり右から数えて3桁ごとに区切りを入れます。この例はASCII数字だけからなる正の整数文字列が対象で、符号、小数、指数表記、各localeの桁区切りには Intl.NumberFormat 等を使います。
ブラウザ・Node.js の互換性
先読み (lookahead)
先読みは長年にわたりJavaScript実装で対応されています。ただし組み込みWebViewなどを含め、「すべての実行環境で懸念なし」とは断定せず、対象環境で確認します。
後読み (lookbehind)
後読みは先読みより導入が遅れましたが、現在の主要ブラウザと現行Node.jsでは利用できます。古いSafari / iOS Safari、旧WebView、Internet Explorer等を対象に含める場合は動かないため、公開時点の互換表だけでなく、実際のサポート範囲とアクセス解析に合わせて判断します。
互換を気にする場合の代替:
// lookbehind: /(?<=@)[\w.-]+/
// 代替: キャプチャで取る
const m = "alice@example.com".match(/@([\w.-]+)/);
m?.[1]; // "example.com"
パフォーマンスの注意
lookaround は便利ですが、lookaround 内外を問わず、曖昧な選択肢や入れ子量化子((a+)+ など)を組み合わせると、入力によっては指数的なバックトラッキングが起きます。lookaround自体はzero-width assertionですが、同じ位置から内部パターンを何度も試す設計では処理量が増えます。
確認点:
- 同じ接頭辞を持つ選択肢や、空文字にも一致する量化子を重ねていないか
- 先読み・後読みの内部で広い
.*を繰り返し評価していないか - 一致しない長い入力や攻撃者が制御できる入力でも、時間とメモリが許容範囲か
「固定長なら常に高速」「lookaheadを並べても常に問題ない」といった実装非依存の保証はありません。代表的な正常入力だけでなく、長い不一致入力でも実測してください。構造化言語を扱うなら専用パーサー、単純な区切り処理なら split 等のほうが読みやすく安全な場合があります。
まとめ
- 4種類の lookaround は「位置を判定する、文字は消費しない」が基本
- 複数の
(?=...)を並べるとAND条件を簡潔に書ける。ただし文字種・長さの一致はパスワード強度を保証しない - 現行の主要JavaScript環境はlookbehindへ対応するが、古いブラウザ・WebViewも対象ならキャプチャグループ等で代替
- 巨大文字列では catastrophic backtracking に注意し、必ず実測
- 書いた正規表現は、本番と同じJavaScriptエンジン・フラグ・代表入力と長い不一致入力で検証する
参考文献・ソース
記事作成に関する注記
本記事は AI(大規模言語モデル)を編集補助として活用して作成しています。 公開前に編集者が内容を確認していますが、事実誤認・仕様の解釈ミス・最新情報との齟齬が含まれる可能性があります。 重要な判断を行う際は、本文中の一次ソースや公式ドキュメントを必ずご自身でご確認ください。 誤りにお気づきの場合は、お問い合わせフォームよりご連絡いただけると助かります。




