🎵

忘れがちなJS正規表現をおさらい

2022/04/30に公開

この記事を書こうと思った経緯

正規表現自体、よく使うが忘れがちなものだと感じるからです。

正規表現はJavaScript問わず色々な言語で利用するものであり、開発者であれば一度は触れたことがあると思います。
ユースケースは、入力値のバリデーション(パスワード、URL形式、住所、郵便番号、、etc)、エディターで特定の記述を検索、ログの解析、など他にも色々あり、幅広いものだと認識しています。

そんな汎用的な知識ですが、個人的に記法が直感的でなく覚えられないことに加え、チームメンバーや周囲のエンジニアから同様の声があったこともあり、見返す用に書き残したいと思ったのが経緯となります。

では、本題に入ります。

正規表現とは?

メタ文字とよばれる記号群を利用し、検索・置換に特化した表現方法です。

メタ文字とは?

よく見る正規表現メタ文字は以下のようなものかな?と思います。

^$?
.+*
/\
[]{}()

→ 細かい記法については以下mdnの記事がまとまっていて良さそうです。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Regular_Expressions#writing_a_regular_expression_pattern

JavaScriptの正規表現を利用するメソッド

メソッド 説明 返り値
exec() 文字列内で一致するものを検索 結果配列。不一致ならnull
test() 文字列内での一致を判定 真偽値
match() 文字列内で一致するものを検索 結果配列。不一致ならnull
matchAll() 文字列内で一致するものを検索 キャプチャグループを含むイテレーター
search() 文字列内での一致を判定 一致した位置。不一致なら-1
replace() 文字列内で一致したものを置換 置換された文字列
replaceAll() 文字列内で一致したものを全て置換 置換された文字列
split() 正規表現または固定文字列を用いて文字列を分割 分割された配列

思いつくユースケースを書いてみた

文字列を配列に分割したい

const str = 'akita,tokyo osaka';
//「,(カンマ)」「空白」を区切り文字に仕様
const result = str.split(/,|\s/);
 
console.log(result);
=> ['akita', 'tokyo', 'osaka']

郵便番号の形式チェックをしたい

const str1 = "114-0211";
const str2 = "1140211";

const pattern = /^[0-9]{3}-[0-9]{4}$/;

console.log(pattern.test(str1));
=> true
console.log(pattern.test(str2));
=> false

特定パターンを含まないか判定したい

const str1 = "This is lemon";
const str2 = "This is apple";

const pattern = /^(?!.*apple).*$/;

console.log(pattern.test(str1));
=> true
console.log(pattern.test(str2));
=> false

(?!パターン)の部分が、 否定先読み という記述であり、これにより「先頭に何かしらの文字列(なくても良い) + 指定パターン」がマッチしないことをチェックできる。その後に .* でなにかしらの文字があってもなくても良い、という意味になるので指定文字列を含まないという意味になる。

メタ文字をエスケープしたい

const str = 'a$a'
// g で全ての一致したメタ文字に対し、第二引数で一致した値をキャプチャし、\を付与
const result = str.replace(/[/\^$.*+?()[\]{}|]/g, '\\$&')

console.log(result) 
=> 'a\$a'

一致した文字列をキャプチャし、$& といった表記で取得し正規表現内で使い回すことで、上記のような処理を実現しています。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/replace#引数としての文字列の指定

記法によるパフォーマンス(番外編)

JavaScriptのRegExpオブジェクトを生成するには、リテラル記法とコンストラクター記法の2通りの方法があり、どちらを使うかによってパフォーマンスが異なるようです。
本記事を書きながら知ったことなので、今後意識して使い分けていこうと思います。

const re = /ab+c/i; // リテラル記法
const re = new RegExp(/ab+c/, 'i') //コンストラクター記法 

リテラル記法

  • 正規表現が評価されるときにコンパイルします。
  • 正規表現が変化しない場合は、リテラル記法を使用してください。
  • 例えばループ内で使用する正規表現を生成するためにリテラル記法を使用すると、反復処理のたびに正規表現を再コンパイルすることはありません。

コンストラクター記法

  • 処理の実行時に正規表現をコンパイルします。
  • 正規表現パターンが変わることがわかっている場合や、パターンが不明でありユーザー入力など別のソースからパターンを取得する場合は、コンストラクター関数を使用してください。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/RegExp#リテラル記法とコンストラクター

Discussion