JavaScriptの文字列マッチングまとめ(indexOf, lastIndexOf)

JavaScriptでよく使う文字列マッチングを、その仕組みも含めて解説。

たとえば、以下のような状況で使用できる。

- ページURLのパス（[`window.location.pathname`](https://developer.mozilla.org/ja/docs/Web/API/window/location)）が特定の文字列で終わるときに●●したい。
- リファラ（[`document.referrer`](https://developer.mozilla.org/ja/docs/Web/API/Document/referrer)）に特定の文字列を含むときに●●したい。
- UserAgent（[`window.navigator.userAgent`](https://developer.mozilla.org/ja/docs/Web/API/NavigatorID/userAgent)）に特定の文字列を含むとき●●したい。


**この記事で扱うマッチング**

- [完全一致](#%E5%AE%8C%E5%85%A8%E4%B8%80%E8%87%B4)
- [前方一致](#%E5%89%8D%E6%96%B9%E4%B8%80%E8%87%B4)
- [部分一致](#%E9%83%A8%E5%88%86%E4%B8%80%E8%87%B4)
- [後方一致](#%E5%BE%8C%E6%96%B9%E4%B8%80%E8%87%B4)
- [正規表現による一致](#%E6%AD%A3%E8%A6%8F%E8%A1%A8%E7%8F%BE%E3%81%AB%E3%82%88%E3%82%8B%E4%B8%80%E8%87%B4)

****************

##完全一致

以下のような処理で完全一致を実現できる。

```js:完全一致
var string  = 'hoge';
var pattern = 'hoge';
if(string === pattern){
  // 完全一致のときの処理
}
```

**解説**
厳密等価演算子`===`を使用してデータ型も含めて完全一致の比較を行う。
等価演算子`==`で完全一致処理を行おうとすると、暗黙の型変換が行われて意図せぬ結果になることがあるので、特別な理由がない限りは厳密等価演算子を使用することをオススメ。

→ [JavaScript 忘れがちな　=== と == の違い](http://qiita.com/PianoScoreJP/items/e43d70ec188c6fed73ed)

****************

##前方一致

以下のような処理で前方一致を実現できる。

```js:前方一致
var string  = 'isogram';
var pattern = 'is';
if(string.indexOf(pattern) === 0){
  // 前方一致のときの処理
}
```

**解説**
[`indexOf()`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/indexOf)はターゲット文字列内の何文字目でパターン文字列が見つかったのか返す。
0文字目,1文字目,..と数え上げるので、最初の文字で見つかった場合は`0`が返ってくる。これを利用して前方一致の判定を実現できる。

###前方一致のコードを短縮する

```js:前方一致_短縮版
var string  = 'isogram';
var pattern = 'is';
if(!string.indexOf(pattern)){
  // 前方一致のときの処理
}
```

JavaScriptにおいて数値を論理値に変換したときに`0`は`false`、それ以外の数はすべて`true`として扱われる。
つまり、`indexOf()`は前方一致したときのみ`false`と見なされる値を返す、そうでないときは`true`と見なされる値を返す性質を持っている。
これを利用して、`indexOf()`の結果を否定演算子`!`により反転すれば短いコードで前方一致の判定ができる。





**前方一致で見つかったとき**
![image](https://qiita-image-store.s3.amazonaws.com/0/103957/290a2e45-30f6-3f72-86c4-b796644884e6.png)


**前方一致で見つからなかったとき**
![image](https://qiita-image-store.s3.amazonaws.com/0/103957/e5718d07-4b4b-c76d-efb6-609b7c2ec74c.png)


**活用例**
→ **[目的のクエリパラメタの値を取得するコード - Qiita](http://qiita.com/AQRiL_1132/items/541340a0bd8c4c930e08)**



###ES6バージョンの前方一致
ES6が使える環境なら、[`startsWith()`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith)でOK。

```js:ES6前方一致
var string  = 'isogram';
var pattern = 'is';
if(string.startsWith(pattern)){
  // 前方一致のときの処理
}
```

****************

##部分一致

以下のような処理で部分一致を実現できる。


```js:部分一致
var string  = 'isogram';
var pattern = 'og';
if(string.indexOf(pattern) > -1){
  // 部分一致のときの処理
}
```

**解説**

[`indexOf()`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/indexOf)では**マッチしなかったとき`-1`**を返す。マッチしたときは`0`以上の整数を返す。これを利用して部分一致の判定を実現できる。

###部分一致のコードを短縮する

```js:部分一致_短縮版
var string  = 'isogram';
var pattern = 'og';
if(~string.indexOf(pattern)){
  // 部分一致のときの処理
}
```

[`~`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Bitwise_NOT)は数値をビット反転する演算子であり、「`indexOf()`でマッチしなかったとき返ってくる数値」である**`-1`をビット反転した数値は`0`になる。


`0`はif文において`false`と判定され、それ以外の数はすべて`true`として扱われるため、
これを利用して、`indexOf()`の結果を`~`でビット反転すれば短いコードで部分一致の判定ができる。

**参考)**
(10進数の)数値xをビット反転した値 = -(x + 1) 

![image](https://qiita-image-store.s3.amazonaws.com/0/103957/2bd6fafe-b721-3864-4cec-fdf75d081a7f.png)

※分かりやすくするために2進数を符号付き4bit整数で表しているが、実際の処理では符号付き32bit整数として処理されている。

**部分一致で見つかったとき**
![image](https://qiita-image-store.s3.amazonaws.com/0/103957/def26b55-983b-fa55-2cb6-6bb13f166d13.png)

**部分一致で見つからなかったとき**
![image](https://qiita-image-store.s3.amazonaws.com/0/103957/60bd1717-5797-0081-3562-11fbd6e7d737.png)

よって、
`~string.indexOf(pattern)`により部分一致が成立する。


**活用例**
→ **[UA情報から電話デバイスかどうかを判定するコード - Qiita](http://qiita.com/AQRiL_1132/items/f7c568d5048c4ba1074e)**
→ **[Cookieをオブジェクト形式で取得するJavaScriptコード - Qiita](http://qiita.com/AQRiL_1132/items/925a7cb04276d9f916d7)**



###ES6バージョンの部分一致
ES6が使える環境なら、[`includes()`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/includes)でOK。

```js:ES6部分一致
var string  = 'isogram';
var pattern = 'og';
if(string.includes(pattern)){
  // 部分一致のときの処理
}
```

****************

##後方一致

以下のような処理で後方一致を実現できる。

```js:後方一致
var string  = 'isogram';
var pattern = 'am';
if((string.lastIndexOf(pattern)+pattern.length===string.length)&&(pattern.length<=string.length)){
  // 後方一致のときの処理
}
```

**解説**

[`lastIndexOf()`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/lastIndexOf)は[`indexOf()`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/indexOf)と同じく、パターン文字列が見つかる場所をターゲット文字列の中から探す関数だが、こちらはターゲット文字列の**後方**から探す。

**indexOf()とlastIndexOf()の動作の違い**

```js:
'aaaa'.indexof('a');  // -> 0
'aaaa'.lastIndexof('a');  // -> 3
```

「後方から探したときにターゲット文字列が見つかった場所 + パターン文字列の長さ」は、後方一致した場合ターゲット文字数と一致する。

![image](https://qiita-image-store.s3.amazonaws.com/0/103957/c911dc0c-8111-4444-6f77-d4f7c30f5485.png)

ただし、この計算は
「ターゲット文字列の長さ+1 = パターン文字列の長さ」であったときも成立するため、
`pattern.length<=string.length`となっていることも確認した上で後方一致の判定を行う。

よって、
`(string.lastIndexOf(pattern)+pattern.length===string.length)&&(pattern.length<=string.length)`により後方一致が成立する。


###ES6バージョンの後方一致
ES6が使える環境なら、[`endsWith()`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith)でOK。

```js:ES6後方一致
var string  = 'isogram';
var pattern = 'am';
if(string.endsWith(pattern)){
  // 後方一致のときの処理
}
```

****************

## 正規表現による一致

以下のような処理で正規表現による一致を実現できる。

```js:正規表現による一致
var string = 'isogram';
var pattern = /^i\w{2}gra\w?$/;
if(pattern.test(string)){
  // 正規表現一致のときの処理
}
```

**解説**

-  [test()](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test)は[正規表現のマッチ](http://qiita.com/yhikishima/items/819a486d8913cd89d1f1)を調べる関数。
-  このとき、patternは文字列ではなく、[正規表現オブジェクト](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/RegExp)であることに注意。
-  行頭記号`^`や行末記号`$`を使用しないと部分一致として作用するので注意。
-  マッチした文字列を取得したい場合は[match()](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/match)や[exec()](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec)を使用。

→[JavaScript - 正規表現のmatchとexec](http://www.m-bsys.com/code/javascript-regexp/match-exec)

**正規表現をチェックするときに参考になる記事**
→ **[正規表現チェックツールまとめ](http://qiita.com/AQRiL_1132/items/c185c7ad84c129e5a2df)**

****************

意図せずES6の便利さがよくわかる記事になってしまいました。
ES6を使えば呪文みたいなコードを書く必要がなくなるので、
使える環境ではES6を使っていきたいです。

**参考になるリンク**
[ES6の概要と、最新ブラウザに対応させる「Babel」の使い方 | ES6のある星に生まれて](https://liginc.co.jp/260178)
[各種ES6環境構築まとめ(Typescriptもあるよ) - Qiita](http://qiita.com/mizchi/items/3bbb3f466a3b5011b509)

**完**