現役エンジニアが教える!実践JavaScript入門 〜正規表現〜
今回扱う内容:正規表現
開発でよく使われる正規表現を理解し、使いこなそう。
正規表現とは
端的に言えばいくつかの文字列を1つの形式で表現するための表現方法です。
ではなぜこの表現方法が有名なのかといえば、この表現方法を利用すれば、たくさんの文章の中から容易に見つけたい文字列を検索できるためです。
https://userweb.mnet.ne.jp/nakama/
正規表現を使うシチュエーション
正規表現をよく使うシチュエーションとしては複雑な条件で一致する文字列を探したい場合があります。例えば携帯電話番号を想定した際に、共通する規則性としては以下の2つが挙げられます。
例:090-1234-5678
- 3桁、4桁、4桁の数字をハイフンでつなぐ
- 先頭の3桁は、「070」、「080」、「090」のいずれかである
この2つの条件を満たす文字列を探すのに、文字列操作や条件比較をして判定するのは難しいですが、正規表現を使うと次のように、シンプルに表現できます。そのため入力フォームでユーザの入力する電話番号が、携帯電話番号のフォーマットに沿っているかをチェックするバリデーションをつける際などに活用できます。
^0[789]0-\d{4}-\d{4}$
正規表現で使用する特殊文字
正規表現では先ほどの携帯番号の例のように、特殊文字を多く用います。1つ1つの特殊文字に意味があり、正規表現ではどのようなものが使われるかは下記のサイトなどをチェックしてみてください。
https://murashun.jp/article/programming/regular-expression.html
ここでは、先ほどの携帯番号の例で使用している正規表現の特殊文字について紹介します。
これらを組み合わせることで携帯番号を表す正規表現を生成できます。
また、先頭と末尾の「^」と「$」を取り外すことで、任意の文字列中に携帯番号の規則を満たす文字列があるかの正規表現を表すこともできます。
^0[789]0-\d{4}-\d{4}$
特殊文字 | 意味 |
---|---|
^ | 文字列の先頭を表す |
[] | 括弧内のいずれか1文字に一致する |
\ | 直後の正規表現記法をエスケープする |
\d | すべての数字を意味する |
{n} | 直前の文字の桁数を指定 |
$ | 文字列の末尾を表す |
正規表現の確認
生成した正規表現が正しいかをWeb上で確認できる正規表現チェッカーというツールをご紹介します。こちらは図のようにチェックしたい正規表現と対象の文字列を入力することで、文字列が正規表現を満たしているかを確認できます。
正規表現チェッカーでの確認
またほかにも英語版のサイトでは次のものもあるので自分に合ったツールを探して使ってみてください。
JavaScriptで正規表現を使用する
この節では次のMDNの記事を参考に、JavaScriptの正規表現について説明します。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Regular_Expressions
正規表現の作成方法
正規表現は2通りの方法で作成できます。
正規表現リテラルを用いた作成方法
1つ目の方法としては、次のように正規表現をスラッシュ「/」で囲まれたパターンからなる正規表現リテラルを使用した方法です。正規表現リテラルはスクリプトの読み込み時にその正規表現をコンパイルします。そのため、正規表現が変化しない場合、この方法を使うとよいパフォーマンスが得られます。
const re = /ab+c/;
RegExpオブジェクトを用いた作成方法
2つ目の方法としては、次のようにRegExp
オブジェクトのコンストラクターを呼ぶ方法です。コンストラクター関数を使用すると、実行時にその正規表現をコンパイルします。正規表現パターンが変わる場合や、パターンが分からない場合、ユーザー入力など別なところからパターンを取得する場合は、コンストラクター関数を使用しましょう。
const re = new RegExp('ab+c');
電話番号の例での正規表現の作成
それでは、先ほどの電話番号の例を用いてJavaScriptで正規表現を作成してみましょう。
正規表現の作成は次のように行います。
const tel = "090-1234-5678";
const re1 = /^0[789]0-\d{4}-\d{4}$/; // 正規表現リテラルを用いた作成方法
const re2 = new RegExp("^0[789]0-\\d{4}-\\d{4}$"); // RegExpオブジェクトを用いた作成方法
注意点としてRegExp
オブジェクトを用いた作成方法の場合、正規表現でバックスラッシュを使うには2回書く必要があります。
次はこれを用いて正規表現に文字列が合致するかというコードを書いてみます。
re1.test(tel); // -> true
re2.test(tel); // -> true
/^0[789]0-\d{4}-\d{4}$/.test(tel); // 正規表現+testメソッドでも可能
test
メソッドは引数で渡した文字列が正規表現に合致するかを判断します。
フラグを用いた高度な検索
正規表現の作成には次の末尾にあるg
のようにフラグと呼ばれるオプションをつけることができます。
正規表現には、このような6種類のオプションフラグがあり、グローバル検索や大文字小文字を区別しない検索などの機能を実現します。適宜検索目的に合わせて組み合わせて使ってみてください。
const re1 = /^0[789]0-\d{4}-\d{4}$/g;
フラグ | 説明 | 対応するプロパティ |
---|---|---|
d | 一致した部分文字列の位置を生成 | RegExp.prototype.hasIndices |
g | グローバル検索 | RegExp.prototype.global |
i | 大文字・小文字を区別しない検索 | RegExp.prototype.ignoreCase |
m | 複数行の検索 | RegExp.prototype.multiline |
s | . が改行文字に一致する | RegExp.prototype.dotAll |
u | パターンを一連の Unicode コードポイントとして扱う | RegExp.prototype.unicode |
y | 対象文字列の現在の位置から始まる部分に一致するものを探す「先頭固定」の検索 | RegExp.prototype.sticky |
正規表現で使えるメソッド
正規表現で使えるメソッドをいくつか紹介します。
execメソッド
exec
メソッドは指定された文字列内で一致するものの検索をします。
例えば、ある正規表現を満たす文字列が存在するかの検索では次のように記述します。
「+」は、「+」前の文字の任意の繰り返しを意味し、今回は「abc」や「abbbbc」などを検索します。
const re = /ab+c/;
re.exec("abccccccccddddabc");
exec
メソッドが返す配列のindex
は0から始まる一致した文字列の位置を表します。ここで先ほど紹介したフラグ「g」をつけてexec
メソッドを2回呼び出した場合と、フラグを付けずにメソッドを2回呼び出した場合のindex
を比較します。
const re = /ab+c/;
re.exec("abccccccccddddabc"); // index -> 0
re.exec("abccccccccddddabc"); // index -> 0
const re_g = /ab+c/g;
re_g.exec("abccccccccddddabc"); // index -> 0
re_g.exec("abccccccccddddabc"); // index -> 15
するとフラグ「g」をつけた場合ではindex
が変化し、フラグを付けなかった場合では変化していないことがわかります。これは、グローバル検索を繰り返すことで1回検索がされた個所から後の文字列から再度検索できます。一方でフラグを付けない場合では1回見検索された時点で終了するので、検索を繰り返しても最初に一致する「abc」へ戻ります。
そのためグローバルの場合は最終まで検索を続けることができ、グローバルでない場合は先頭で見つかったら終わるというように検索の方法が異なります。
testメソッド
test
メソッドは文字列内で一致するものがあるかを検査し、trueまたはfalseを返します。このメソッドは正規表現で合致するメソッドがあるかの判定に使われる一般的なメソッドです。
電話番号の例でも使いましたが、使い方は次の通りです。
/ab+c/.test("abccccccccddddabc"); // -> true
matchメソッド
match
メソッドはキャプチャグループを含む、一致したすべての文字列を含む配列を返します。exec
メソッドとtest
メソッドは正規表現オブジェクトのメソッドですが、match
メソッドは文字列オブジェクトのメソッドであり、次のように引数に正規表現を渡します。
またグローバルオプションを付けた場合では、結果として一致した文字列の配列が返され、グローバルオプションを付けない場合ではexec
メソッドと同じ配列が返ることに注意しましょう。
"abccccccccddddabc".match(/ab+c/g); // -> ['abc', 'abc']
"abccccccccddddabc".match(/ab+c/); // -> execメソッドと同じ結果
matchAllメソッド
matchAll
メソッドは、match
メソッドと似た使い方をするのですが、結果としてキャプチャグループを含む、一致したすべての文字列を含む反復子を返します。反復子はfor...of
との相性がよいので、一致したものに対し何らかの処理を行う場合などにmatchAll
メソッドは使われます。
for(const v of "abccccccccddddabc".matchAll(/ab+c/g)){
console.log(v); // v -> execメソッドと同じ結果
}
searchメソッド
search
メソッドは、文字列で一致するものがるかを検査し、一致した位置を返します。検索に失敗した場合は-1を返します。そのためどの位置に一致する文字列があるかを扱いたいときに使用します。使い方は次の通りです。
"abccccccccddddabc".search(/ab+c/); // -> 0
"hogeabccccccccddddabc".search(/ab+c/); // -> 4
replaceメソッド
replaceメソッドは、文字列内で一致するものを1つ検索し、一致した部分文字列を置換します。使い方は次の通りです。グローバルオプションを付けた際には文字列内で一致するすべての部分文字列が置換されます。
"abccccccccddddabc".replace(/ab+c/, "hoge"); // -> hogecccccccddddabc
"abccccccccddddabc".replace(/ab+c/g, "hoge"); // -> hogecccccccddddhoge
replaceAllメソッド
replaceAllメソッドは、文字列内で一致するものをすべて検索し、一致した部分文字列を置換します。使い方は次の通りです。注意点として引数に渡す正規表現にはグローバルオプションを付けていないとエラーになるので明示的にreplace
メソッドと分けて使いましょう。
"abccccccccddddabc".replaceAll(/ab+c/g, "hoge"); // -> hogecccccccddddhoge
"abccccccccddddabc".replaceAll(/ab+c/, "hoge"); // -> エラー
splitメソッド
splitメソッドは、正規表現または固定文字列を用いて文字列を分割し、部分文字列の配列に入れます。使い方は次の通りです。正規表現で使うだけでなくsplit
メソッドは「,」区切りで文字列を分解する際などにもつかわれます。
"abccccccccddddabc".split(/ab+c/); // -> ['', 'cccccccdddd', '']
正規表現を使う際の注意点
正規表現を使う際の注意点としては1つ目に使用している正規表現が正しいかがあります。
ネットで調べた正規表現を使って処理を実行する際に、使われている正規表現が本当に正しいかを確かめてから使うようにしましょう。
2つ目の注意点としてはよく使うバリデーションはライブラリを使ったほうが良い場合もあるということです。JavaScriptのバリデーションライブラリとしてはvalidator.jsなどがあります。
https://github.com/validatorjs/validator.js/
まとめ
今回は正規表現について解説しました。JavaScriptだけでなく広く使われる技術ですのでぜひマスターしましょう。