【JavaScript】初学者「正規表現の学習ってなんとなく避けてきた…」って人のために

こんにちは。masakichiです。

コードを書いていると、正規表現ってわりと出くわすけど、なんとなくで理解している方も多いのではないでしょうか？

わたしはそうでした。

しかし、さすがにいつまでもこのままではダメかなと思い、基本的なところから学習してみました。
せっかく調べたので、同じような悩みを持っている同志に向けて、記事をまとめてみました。

## 正規表現とは？
文字列内の組み合わせを照合するために用いられるパターンのことです。

例えば、`I bought an apple`という文字列から、`apple`という文字列が含まれているか確認したい場合、以下のように記述します。

```javascript
let str = 'I bought an apple';
let reg = /apple/;
let result = reg.test(str);
console.log(result);
// >> true
```

上記では、変数regにスラッシュで囲まれた`/apple/`という謎の文字列を格納しています。実はこの謎の文字列こそが、正規表現のために用いられるパターンを表しています。

このパターンを使うことで、`I bought an apple`というテキストに`/apple/`というパターンが含まれているか確認することができるのです。

詳しくは後ほど徐々に記載していきますが、これを応用していくと、特定の文字列が「メールアドレスの形式なのか？」とかを照合できるようになります。

## 前提知識 RegExpオブジェクトについて
あまり意識することはないですが、JavaScriptには自分で何も定義しなくても使えるオブジェクトがたくさん用意されています。
これをビルトインオブジェクトともいいます。

ビルトインオブジェクトの説明は[こちら](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects)をご覧ください。

さて、JavaScriptにおける正規表現では、**RegExpオブジェクト**というものを使います。
RegExp オブジェクトもビルトインオブジェクトのひとつで、あるパターンでテキストを検索するために利用されます。

オブジェクト？？？

といきなり言われても、なかなかキャッチアップしにくいところもあるかと思いますが、冒頭の例で記述した`/apple/`のことだとイメージしておいてください。

## 正規表現（RegExp オブジェクト）を作る
RegExp オブジェクトを生成するには 2 通りの方法があります。各々確認していきます。

### リテラル記法
パターンをスラッシュで囲む記法です。スラッシュで囲まれた文字列が正規表現パターンとして利用されます。以下のように記述します。

```javascript
let reg = /ab+c/;
```

### RegExpオブジェクトのコンストラクタ関数を呼び出す
以下のように RegExp オブジェクトのコンストラクター関数を呼び出す方法です。
一般的に引数はスラッシュで囲むのではなく、引用符を使用します。

```javascript
let reg = new RegExp('ab+c'); 
```

### どっちで書いたらいいですか？
結論から述べると、正規表現のパターンが後から変化したり、ユーザー入力値などの別ソースからパターンを取得する場合はコンストラクタ関数を使い、それ以外はリテラル記法を使います。

リテラル記法はスクリプトが読み込まれ時にその正規表現をコンパイルするため、後からパターンが変化することはありません。
結果として、パフォーマンス的にはコンストラクタ関数よりも向上するとされています。

しかし、一方で正規表現の中で変数を使いたいといった場合もあるでしょう。
そんな時はコンストラクタ関数を使います。

 例えば、元々の配列からユーザー入力した値を含むテキストだけの配列にしたい場合は下記のようにします。
```html
<input type="text" name="fruits" id="fruits">
<button id="button">Click</button>
```

```javascript
const fruits = ['apple','orange','melon','watermelon'];
const inputText = document.getElementById('fruits');
const button = document.getElementById('button');

button.addEventListener('click',()=>{
	let s = inputText.value;//ユーザー入力値を変数に格納
	let reg = new RegExp(s);//変数をコンストラクタ関数に渡す
    
    //filter関数で新しい配列を生成
	let filteredFruits = fruits.filter((fruit)=>(
		reg.test(fruit)
	));
	console.log(filteredFruits);
    // >> ユーザー入力値:'melon'
    // >> filteredFruits = ['melon','watermelon']
})
```

途中でユーザー入力値を変数に格納しています。これがリテラル形式だとできません。
このように後から正規表現のパターンが変化する場合はコンストラクタ関数を使います。

## 実際に正規表現を使ってみる
ここまでは、正規表現の基礎的なことを書いてきました。
ここからは、実際に正規表現を使ってみることにします。

### 直接一致する文字を照合してみる
一番シンプルな方法が直接一致する文字を正規表現で指定して、照合するパターンです。

例えば、`/Morning/`（正規表現はスラッシュで囲むのでしたね）というパターンは、文字列の中で`/Morgin/`が正確に一致する組み合わせを表しています。`Hi! Good Morning!`や`I have to wake up early Morning.`などの文字列には一致しますが、`Good Night...`には一致しません。

```javascript
const reg = /Morning/;

console.log(reg.test('Hi! Good Morning!'));
// >> true
console.log(reg.test('I have to wake up early Morning.'));
// >> true
console.log(reg.test('Good Night...'));
// >> false
```

ちなみに上記で使っている、testメソッドはRegExpオブジェクトに使えるメソッドで、引数に受け取った文字列内で一致するものがあるかテストして、真偽値を返します。

### 特殊文字を使って照合する
直接一致する文字を指定する方法以外に、特殊文字を使う方法があります。
特殊文字を使うことで、1個以上の数値を探したり、3回以上繰り返す文字を含めた文字列を探したりすることができます。

と言われても、実際に確認しないとイメージしづらいかと思うので、3つほど具体例を見ていきます。

#### `.`：あらゆる 1 文字とマッチする
`.`は改行文字を除くあらゆる 1 文字とマッチします。
```javascript
const reg = /a.c/;

console.log(reg.test('abc'));
// >> true
console.log(reg.test('acc'));
// >> true
console.log(reg.test('abb'));
// >> false
```

#### `\d`：あらゆる数字にマッチする
`\d`はあらゆる（アラビア）数字にマッチします。`[0-9]` に相当します。
```javascript
const reg = /\d/;

console.log(reg.test('I have 2 books.'));
// >> true
console.log(reg.test('I have 32 years old.'));
// >> true
console.log(reg.test('I have an apple.'));
// >> false
```

#### `[xyz][a-c]` : 角括弧で囲まれた文字のいずれか 1 個にマッチする
角括弧で囲まれた文字のいずれか 1 個にマッチします。ハイフンを用いて文字の範囲を指定することも可能です。例えば `[abcd]` は` [a-d]` と同じ意味になります。

```javascript
const reg = /[a-z]/;

console.log(reg.test('abc'));
// >> true
console.log(reg.test('123'));
// >> false
console.log(reg.test('あいう'));
// >> false
```

### 特殊文字はたくさん種類がある
特殊文字に関しては種類がたくさんあるので、この記事で全てを紹介することはしません。
下記に一覧がまとまっていますので、適宜確認していただくようお願いします。

[![reg.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1006362/138e7e48-7456-0a80-642b-b845000f03b4.png
)](https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Regular_Expressions#using_special_characters)MDN : [正規表現より](https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Regular_Expressions#using_special_characters)

### エスケープ（特殊文字を文字として扱いたい）
特殊文字が便利なことはなんとなく感じることができたかと思います。

ただ、特殊文字を文字として扱う場合もあるでしょう。
例えば、ドット`.`を正規表現で検索したい場合などです。（`.`はあらゆる 1 文字とマッチする特殊文字でした）

そんなときは、特殊文字の前にバックスラッシュを付けることで、エスケープ（特殊文字を特殊文字として扱わなくする）する必要があります。

例えば、以下のようにドットをエスケープしている正規表現とそうでないものを2つ用意したとします。
変数`reg`のほうでは、ドットをエスケープしていないので、特殊文字として扱われ`google@com`もtrueとみなされます。
変数`reg02`でドットをエスケープしているので、文字として扱われ`google@com`はfalseとみなされます。

```javascript
let reg = /google.com/;
let reg02 = /google\.com/;

console.log(reg.test('google.com'));
// >> true
console.log(reg.test('google@com'));
// >> true
console.log(reg02.test('google.com'));
// >> true
console.log(reg02.test('google@com'));
// >> false
```

また、エスケープする上でRegExpコンストラクターに文字列を渡して使用する場合、少し注意が必要です。
RegExpコンストラクターでは、スラッシュではなく引用符で正規表現を囲むので、`new RegExp('a\.b')`は`/a.b/`と同等になります。

```javascript
let reg = new RegExp('a\*b');
let reg02 = new RegExp('a\\*b');

console.log(reg);
// >> /a*b/
console.log(reg02);
// >> /a\*b/
```

上記の場合

1. 引用符で囲んである
2. JavaScriptがこのバックスラッシュがついてるのは文字列リテラルのエスケープ文字、つまり特殊文字なんだと解釈する
3. いや、そうじゃないよということでバックスラッシュをもう一つ追加
4. エスケープされて文字列になる

みたいな感じで、特殊文字にエスケープされたものをもう一回エスケープして文字列に戻すみたいなイメージです。

### フラグを用いた高度な検索（正規表現にオプションを追加する）
正規表現には、特殊文字以外にも大文字小文字を区別しない検索などの機能を実現する6種類のオプションフラグというものがあります。
これらのフラグは、個別に使用することも一緒に使用することもでき、順序は問いません。

フラグ一覧は下記より確認ができます。

[![02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1006362/5cb49b6d-9950-d49c-2638-1881ac992bee.png)
](https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Regular_Expressions#advanced_searching_with_flags)MDN : [正規表現より](https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Regular_Expressions#advanced_searching_with_flags)

フラグを正規表現に含めるには、次のようにします。

正規表現リテラルの場合
```javascript
let reg = /pattern/flags;
```
関数コンストラクタの場合
```javascript
let reg = new RegExp('pattern', 'flags');
```

## 実践に近い形式で正規表現を使ってみる
次に実際に使いそうな正規表現を特殊文字を駆使しつつ、紹介してみようと思います。

#### メールアドレスを表現する
よくあるのが入力フォームに入力されたメールアドレスが正しい形式なのかをチェックするということ。

メールアドレスをチェックしたい場合は下記のように記述します。

```javascript
let reg = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/i;

console.log(reg.test('abc@example.com'));
// >> true
console.log(reg.test('abc123@example.co.jp'));
// >> true
console.log(reg.test('abc@com'));
// >> false
```

正規表現を分解すると下記のパーツに分けることができます。（基本的に正規表現は分解すると解読しやすいです）

 **・`^`について**
 入力の先頭にマッチする特殊文字です。例えば /^A/ は "an A" の 'A' にはマッチしませんが、"An E" の 'A' にはマッチします。
 
**・`[^\s@]+`について**
スペース、タブ、改ページ、改行を含むホワイトスペース文字と@マーク以外の文字列の1回以上の繰り返しにマッチします。`[]`は角括弧で囲まれた文字のいずれか 1 個にマッチし、角括弧内の`^`はその否定を表します。`\s`はスペース、タブ、改ページ、改行を含むホワイトスペース文字、`@`は@マークのことです。最後の+は直前の項目の1 回以上の繰り返しに一致します。

 **・`@`について**
 @マークにマッチ。
 
**・`[^\s@]+`について**
上記に同じ。

**・`\.`について**
エスケープされたドットです。

**・`[^\s@]{2,}`について**
前半部分は上記に同じです。`{2,}`は直前の項目の少なくとも2回の出現に一致します。

**・`$`**
入力の末尾にマッチします。例えば /t$/ は "eater" の "t" にはマッチしませんが、"eat" の "t" にはマッチします。

**・`i`**
オプションフラグです。大文字・小文字を区別しないで検索するようになります。

## JavaScriptにおける正規表現を使用するメソッド
ここまでで、だいぶ正規表現を身近に感じることができるようになったのではないでしょうか？
最後に、JavaScriptにおいて正規表現を使用することができるメソッドを紹介します。

JavaScirptでは以下のメソッドで正規表現を使用することができます。

| オブジェクト | メソッド |
| --- | --- |
| RegExp | exec() , test() |
| String | match() , matchAll() , replace() , replaceAll() , search() , split() |

数は多くありませんので、実際に[MDNのサイト](https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Regular_Expressions#working_with_regular_expressions)などで使用方法を確認してみてください。

## おわりに
正規表現、いままでなんとなく避けてきたけど、そこまで難しいものでもないなと思いました。

また、個人的に学習した内容を記事にしたものなので、正確でない情報が記載されている場合もございます。

もし、ご指摘箇所などございましたらコメントやプルリクくだされば嬉しい限りです。もっと正確で細かな情報を知りたいよという方がいらっしゃいましたら是非下記参考リンク先を覗いてみてください。

## 参考リンク先
- [MDN Web Docs - 正規表現](https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Regular_Expressions)
- [MDN Web Docs - RegExp](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/RegExp)