
正規表現の先読み・後読み完全ガイド — (?=...) と (?<=...) を実務で使い分ける
正規表現の先読み(lookahead)と後読み(lookbehind)は、マッチ位置を判定するためだけに使い、結果の文字列には含めないという特殊な構文です。「この文字の"直後"に〜があるとき」「この文字の"直前"に〜があるとき」といった条件を、キャプチャグループを汚さずに書けるのが強みです。本記事では 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
「HTMLタグ以外のテキストを取得」「単語境界で終わる数字だけ」といった"除外"系は否定 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 結合
「英数字混在のパスワード(数字と英字が両方含まれ、8文字以上)を一括判定」は 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桁ごとに区切りを入れる、という挙動です。
ブラウザ・Node.js の互換性
先読み (lookahead)
すべてのモダンブラウザ・Node.js で古くから動作します。互換性の懸念はありません。
後読み (lookbehind)
歴史的経緯から対応が少し遅れていました。
- Chrome 62+ (2017年), Edge 79+, Firefox 78+ (2020年), Safari 16.4+ (2023年)
- Node.js 10+ で対応
iOS Safari を広くサポートするサイトでは、2023年春以前のiOSユーザーでは lookbehind が動かないことに注意。IE 対応は不可能です(IE は 2022 年 6 月にサポート終了)。
互換を気にする場合の代替:
// lookbehind: /(?<=@)[\w.-]+/
// 代替: キャプチャで取る
const m = "alice@example.com".match(/@([\w.-]+)/);
m?.[1]; // "example.com"
パフォーマンスの注意
lookaround は便利ですが、lookaround 内のパターンに入れ子量化子((a+)+ など)があると、巨大な文字列で指数的に遅くなるケースがあります(catastrophic backtracking)。lookaround それ自体は zero-width assertion で文字を消費しないため直接の原因にはなりにくく、問題は中身の量化子設計にあります。
目安:
- 固定長の lookbehind(
(?<=abc)): 高速 - 可変長の lookbehind(
(?<=.*abc)): 実装次第で遅い(V8は最適化あり) - 複数の lookahead 直列: 基本的に高速(パスワード判定など)
数MB以上の長文に対して正規表現を使う場合は、必ず実測してください。代替としてパース用ライブラリ(peggy, nearley 等)や文字列 split + filterのほうが高速なこともあります。
まとめ
- 4種類の lookaround は「位置を判定する、文字は消費しない」が基本
- 複数の
(?=...)を並べると and 条件が簡潔に書ける(パスワード検証の定番) - lookbehind は iOS Safari 16.4 未満で動かない。互換が必要ならキャプチャグループで代替
- 巨大文字列では catastrophic backtracking に注意し、必ず実測
- 書いた正規表現は本記事のツールや regex101 等で必ず動作検証する
参考文献・ソース
記事作成に関する注記
本記事は AI(大規模言語モデル)を編集補助として活用して作成しています。 公開前に編集者が内容を確認していますが、事実誤認・仕様の解釈ミス・最新情報との齟齬が含まれる可能性があります。 重要な判断を行う際は、本文中の一次ソースや公式ドキュメントを必ずご自身でご確認ください。 誤りにお気づきの場合は、お問い合わせフォームよりご連絡いただけると助かります。


