正規表現のきほん
このレッスンで分かること
- 正規表現リテラルの書き方と、
testで当たるかどうかを調べる方法\dや\wといった文字クラスと、+*{n}といった量指定子の読み方matchにgフラグを付けて、当たった部分をすべて取り出す方法
正規表現のきほん とは
文字列を「形」で探すための小さな言語です。文字が入っているかではなく、4 桁の数字が並んでいるか、といった構造で探せるようになります。
includes では書けないもの
入門の第9章で、お問い合わせフォームの入力チェックを書きました。あのときの道具は value.length と includes の 2 つだけでした。
if (name.length === 0) {
formError.textContent = "お名前を入力してください";
}これで足りるのは「空かどうか」「あの文字が入っているか」までです。学習ログの本文から年号だけを抜き出したい、と言われた瞬間に手が止まります。includes("2026") は 2026 という決め打ちの文字を探すもので、「4 桁の数字」という形を探すものではないからです。
正規表現は、この形を書くための記法です。JavaScript では、スラッシュで挟んで書きます。
const pattern = /\d{4}/;
console.log(pattern.test("2026年に公開しました")); // true
console.log(pattern.test("今年に公開しました")); // false/\d{4}/ は「数字がちょうど 4 個並んでいる箇所」という意味です。test は、その形が文字列のどこかにあれば true、無ければ false を返します。
スラッシュで挟んだ書き方を正規表現リテラルと呼びます。
new RegExp("\\d{4}")と書く方法もありますが、こちらは文字列を経由するぶんバックスラッシュを二重に書く必要があり、読みにくくなります。パターンが固定なら、リテラルで書くほうが安全です。
パターンの読み方
1 文字ぶんの種類を表す書き方と、それが何個続くかを表す書き方の 2 つを覚えると、たいていのパターンは読めるようになります。
文字クラスで種類を指定する
1 文字ぶんの「種類」を表す書き方を文字クラスと呼びます。よく使うものは次の表のとおりです。
| 書き方 | 当たる 1 文字 | 当たる例 |
|---|---|---|
\d | 数字 | 0 7 |
\w | 半角英数字とアンダースコア | a Z 3 _ |
\s | 空白文字。半角スペース、タブ、改行 | |
[abc] | 角かっこの中のどれか 1 文字 | a b c |
[a-z] | 範囲指定。ここでは英小文字 | k |
[^abc] | 角かっこの中以外の 1 文字 | x 1 |
. | 改行以外のどれか 1 文字 | 何でも |
\d を大文字にした \D は「数字以外」というふうに、大文字は反対の意味になります。\W \S も同じ関係です。
[^...] の ^ は「以外」を表します。角かっこの外に出てくる ^ は意味が変わるので、次のレッスンで扱います。
量指定子で個数を指定する
文字クラスの後ろに置いて、それが何個続くかを指定します。
| 書き方 | 意味 |
|---|---|
+ | 1 個以上 |
* | 0 個以上 |
? | 0 個か 1 個 |
{3} | ちょうど 3 個 |
{2,4} | 2 個以上 4 個以下 |
{2,} | 2 個以上 |
組み合わせると、意図がそのまま形になります。
console.log(/\d+/.test("作品は12件")); // true
console.log(/[A-Z]{2,}/.test("HTML")); // true
console.log(/[A-Z]{2,}/.test("Html")); // false
console.log(/^\d*$/.test("")); // true3 行目が false になるのは、Html に大文字が 1 個しか続いていないためです。「2 個以上」という指定がそのまま効いています。
候補を並べる
縦棒はどちらか一方という意味になります。タグ名のように、決まった語のどれかを探したいときに使います。
const tags = "HTML と CSS と JavaScript";
console.log(tags.match(/HTML|CSS/g)); // ["HTML", "CSS"]縦棒は左右をできるだけ広く取るので、範囲を区切りたいときは丸かっこで囲みます。
console.log("公開日と更新日".match(/(公開|更新)日/g)); // ["公開日", "更新日"]丸かっこには囲む以外の役割もありますが、それは置換のレッスンで扱います。ここでは「範囲を区切るもの」として使います。
当たった部分を取り出す
test は当たるかどうかしか返しません。当たった文字そのものが欲しいときは match を使います。
const text = "2024年に始めて2026年に公開しました";
console.log(text.match(/\d{4}/)); // ["2024", index: 0, ...]
console.log(text.match(/\d{4}/g)); // ["2024", "2026"]末尾の g はグローバルフラグです。付けないと最初の 1 件で探すのをやめます。付けると全部を集めた配列が返ります。
フラグはスラッシュの後ろに続けて書きます。よく使うものは次の 2 つです。
| フラグ | 意味 |
|---|---|
g | 最初の 1 件で止めず、すべて探す |
i | 大文字と小文字を区別しない |
console.log("HTML と html".match(/html/gi)); // ["HTML", "html"]
matchは当たらなかったとき、空配列ではなくnullを返します。ここが一番の落とし穴です。返り値をそのままmapにつなぐと、Cannot read properties of nullで落ちます。
正規表現を使わない判断もする
正規表現は強力なぶん、読む側の負担が大きい書き方です。次のような場面では、素直な文字列操作のほうが後で困りません。
| やりたいこと | 向いている書き方 |
|---|---|
| 決まった語が入っているか | includes |
| 決まった語で始まるか | startsWith |
| 決まった語で終わるか | endsWith |
| 桁数や並びといった形を見る | 正規表現 |
title.includes("プロフィール") を /プロフィール/.test(title) に書き換えても、何も良くなりません。形を見たいときにだけ持ち出す道具だと考えてください。
半年後の自分が読めないパターンは、書かないほうがましです。どうしても長くなるときは、名前を付けた変数に入れて、何を探しているかをコメントではなく変数名で伝えます。
課題の考え方
このレッスンでは、学習ログの本文から年号をすべて抜き出して、数値の配列にして返します。手順は 3 つです。
/\d{4}/gでmatchする- 返り値が
nullかどうかを確かめ、nullなら空配列を返す - 当たった文字列は
"2026"のような文字列なので、数値に直す
3 つめは map(Number) で書けます。Number は文字列を数値に変える関数なので、そのまま map に渡せます。
console.log(["2024", "2026"].map(Number)); // [2024, 2026]よくある間違い
match の null を忘れる
当たらなかったときに null が返ることを忘れると、その場では動いて後で落ちます。
// NG 年号を含まない文字列を渡すと落ちる
function extractYears(text) {
return text.match(/\d{4}/g).map(Number);
}
// OK 先に null を確かめる
function extractYears(text) {
const matches = text.match(/\d{4}/g);
if (matches === null) {
return [];
}
return matches.map(Number);
}. をそのまま書く
正規表現の . は「どれか 1 文字」です。ピリオドそのものを探したいときはバックスラッシュを付けます。
console.log(/example.com/.test("exampleXcom")); // true 意図と違う
console.log(/example\.com/.test("exampleXcom")); // false こちらが正しい{4} を「4 桁ちょうど」だと思い込む
/\d{4}/ は「4 個並んだ箇所がある」という意味であって、「全体が 4 桁」ではありません。"12345" にも当たります。全体を縛りたいときは、次のレッスンで扱う ^ と $ が要ります。
console.log(/\d{4}/.test("12345")); // true
console.log(/^\d{4}$/.test("12345")); // false要件
- 関数名は extractYears、引数は文字列の text を 1 つ受け取る
- 4 桁の数字に当たる部分をすべて取り出し、出てきた順に並べる
- 戻り値は数値の配列にする。文字列のままにしない
- 1 つも見つからないときは空配列を返す
入出力例
extractYears("2024年に始めて2026年に公開しました") → [2024,2026]
extractYears("学習ログを書いています") → []
extractYears("") → []
extractYears("作品は 12 件です") → []
extractYears("1998と2026と2030の記録") → [1998,2026,2030]