この記事はChrome55でいろいろ試行錯誤した時のメモ
同様の記事はいっぱいあるはず
使用頻度の高そうなグローバル正規表現 g オプションフラグをメインに
意図していなかった処理を行いそうな動作が気になったので各メソッドで動作を調べた
RegExpとStringの正規表現関係メソッド
RegExpオブジェクトのメソッド
regexp.test(string)
regexp.exec(string)
Stringオブジェクトのメソッド
string.match(regexp)
string.search(regexp)
string.split(regexp)
string.replace(regexp)
正規表現パターン
パターン | 説明 |
---|---|
/abc/ | "abc" にマッチ |
/[abc]/ | "a" か "b" か "c" にマッチ |
/[a-c]/ | "a" から "c" にマッチ |
/[^abc]/ | "a" でも "b" でも "c" でもない文字にマッチ |
/^abc/ | ローカル正規表現では文字列の頭を表し、グローバルでは行頭を表す |
/abc$/ | ローカル正規表現では文字列の尻を表し、グローバルでは行末を表す |
/a?/ | 0回か、1回だけ出現する |
/a+/ | 1回以上繰り返し |
/a*/ | 0回以上繰り返し |
/a{5}/ | 5回の繰り返し |
/a{5,10}/ | 5回以上10回以下の繰り返し |
/a{5,}/ | 5回以上の繰り返し |
控えめなマッチ | 繰り返し演算子は最大数マッチしようとするが ? を付けることにより最小限のマッチで済まそうとする "abcabcab".match(/a.*c/) => ["abcabc"] "abcabcab".match(/a.*?c/) => ["abc"] |
/(ab)/ | 文字列のグループ化 副次効果でキャプチャも行う |
/(?:ab)/ | 文字列のグループ化 キャプチャを行わない |
/a(?=b)/ | bが続くaにマッチ(肯定的先読み) |
/a(?!b)/ | b以外が続くaにマッチ(否定的先読み) |
/(?<=b)a/ | bに続くaにマッチ(肯定的後読み) |
/(?<!b)a/ | b以外に続くaにマッチ(否定的後読み) |
/\t/ | タブ |
/\r/ | キャリッジリターン |
/\f/ | ラインフィード |
/\n/ | 改行 |
/\uxxxx/ /\u{xxxx}/u |
ユニコード文字 |
/./ |
改行以外のあらゆる文字 [ES2018]でsオプションが追加された |
/\d/ | [0-9] すべての数字 |
/\D/ | [^0-9] 数字以外のあらゆる文字 |
/\w/ | [A-Za-z0-9] すべての英数字 |
/\W/ | [^A-Za-z0-9] 英数字以外のあらゆる文字 |
/\s/ | ユニコード空白文字(スペース, 全角スペース, タブ, 改行 等) |
/\S/ | ユニコード空白文字以外のあらゆる文字 |
/\b/ | 単語の境界にマッチする(スペース、句読点 等) |
/\B/ | /b の逆 |
/moko|hage/ | moko か hage |
/moko(A|B)/ | mokoA か mokoB (カッコは副次効果でキャプチャも生成する) |
/moko(?:A|B)/ | mokoA か mokoB (キャプチャを生成しない) |
/moko(A|B)hage\1/ | mokoAhageA か mokoBhageB (キャプチャを後方参照する) |
$1, $2, $3... | 変換後文字にて後方参照を指定する(キャプチャを利用する) |
$& | 変換後文字にて後方参照を指定する(マッチ文字列) |
正規表現オブジェクトの作り方
const regexp1 = new RegExp("[AB]0[12]", 'gmi');
正規表現リテラルでも記述できる
ていうか通常こっちを使っているとは思いますが
const regexp2 = /[AB]0[12]/;
const regexp3 = /[AB]0[12]/gmi;
使いそうな正規表現オプションフラグ
g
- global
- 検索文字列全体について繰り返して一致を検索する
- グローバルじゃない方はローカル正規表現と言っている書籍も有ったんでそう呼んでます
i
- ignoreCase
- 大文字と小文字の違いを無視
m
- multiline
- 行をまたいだ検索を行う
u
[ES2015]で追加されたぞらしいぞ!
使わない理由は無いように思えるぞ!
使いそうな正規表現オブジェクトのプロパティ
lastIndex
グローバル正規表現時に次のマッチが始まる位置を保持している
source
フラグ無しの正規表現パターンを格納している => "[AB]"
regexp#test(string)
マッチするかどうかが知りたいだけならこれ
// ローカル正規表現
const regexp4 = /moko\d+/
regexp4.test("moko123 moko456")
// => true
// グローバル正規表現
const regexp5 = /moko\d+/g;
regexp5.test("moko123 moko456")
// => true
グローバル正規表現オブジェクトはlastIndexを保持しているので、同メソッドを繰り返し実行すると結果に違いが出てくる
regexp4.lastIndex
// => 0
regexp5.lastIndex
// => 7
regexp4.test("moko123 moko456")
// => true
regexp5.test("moko123 moko456")
// => true
regexp4.lastIndex
// => 0
regexp5.lastIndex
// => 15
regexp4.test("moko123 moko456")
// => true
regexp5.test("moko123 moko456")
// => false <== マッチしない
regexp4.lastIndex
// => 0
regexp5.lastIndex
// => 0 <== リセットされた
regexp4.test("moko123 moko456")
// => true
regexp5.test("moko123 moko456")
// => true <== またマッチするようになった
こんな使い方をした事はないけど、予想外の動作をする可能性がある
const regexp6 = /moko\d+/g;
regexp6.test("moko123")
// => true
regexp6.test("moko456")
// => false <== あー
regexp#test(string) ではグローバル正規表現を使わない方がいいぞ!
regexp#exec(string)
マッチオブジェクト(プロパティを持った配列)が取得できる
マッチしなかった場合はnullが返ってくる
// ローカル正規表現
const regexp7 = /moko\d+/
regexp7.exec("moko123 moko456")
// => ["moko123", index: 0, input: "moko123 moko456"]
regexp7.exec("moko123 moko456")
// => ["moko123", index: 0, input: "moko123 moko456"] <== 何度実行しても戻り値は変わらない
regexp7.exec("")
// => null
// グローバル正規表現
const regexp8 = /moko\d+/g
regexp8.exec("moko123 moko456")
// => ["moko123", index: 0, input: "moko123 moko456"]
regexp8.exec("moko123 moko456")
// => ["moko456", index: 8, input: "moko123 moko456"] <== おっ
regexp8.exec("moko123 moko456")
// => null <== えっ
regexp8.exec("moko123 moko456")
// => ["moko123", index: 0, input: "moko123 moko456"] <== あっ
regexp8.exec("")
// => null
キャプチャしている場合、キャプチャのカッコ部分にマッチした部分も配列内に配置される
// ローカル正規表現
const regexp9 = /moko(\d+)/
regexp9.exec("moko123 moko456")
// => ["moko123", "123", index: 0, input: "moko123 moko456"]
regexp9.exec("moko123 moko456")
// => ["moko123", "123", index: 0, input: "moko123 moko456"] <== 何度実行しても戻り値は変わらない
// グローバル正規表現
const regexp10 = /moko(\d+)/g
regexp10.exec("moko123 moko456")
// => ["moko123", "123", index: 0, input: "moko123 moko456"]
regexp10.exec("moko123 moko456")
// => ["moko456", "456", index: 8, input: "moko123 moko456"] <== おっ
regexp10.exec("moko123 moko456")
// => null <== えっ
regexp10.exec("moko123 moko456")
// => ["moko123", "123", index: 0, input: "moko123 moko456"] <== あっ
regexp#exec(string) ではグローバル正規表現は便利だが注意が必要だ!
string#match(regexp)
ローカル正規表現の場合、マッチオブジェクト(プロパティを持った配列)が取得できる
マッチしなかった場合はnullが返ってくる
// ローカル正規表現
const regexp11 = /moko(\d+)/
"moko123 moko456".match(regexp11)
// => ["moko123", "123", index: 0, input: "moko123 moko456"]
"moko123 moko456".match(regexp11)
// => ["moko123", "123", index: 0, input: "moko123 moko456"] <== 何度実行しても戻り値は変わらない
"".match(regexp11)
// => null
グローバル正規表現の場合、マッチした文字列を集めた配列が取得できる
戻り値にキャプチャは影響を与えない
正規表現オブジェクトは引数なので変更されたりしないのか、lastIndexも気にしなくていいぽい
マッチしなかった場合は空配列ではなくnullが返ってくる
// グローバル正規表現
const regexp12 = /moko(\d+)/g
"moko123 moko456".match(regexp12)
// => ["moko123", "moko456"]
"moko123 moko456".match(regexp12)
// => ["moko123", "moko456"] <== 何度実行しても戻り値は変わらない
"".match(regexp12)
// => null <== うーん
string#match(regexp) ではローカル正規表現もグローバル正規表現も、両方便利なものだ!
string#search(regexp)
文字列にマッチした位置(index)が取得できる
戻り値にキャプチャは影響を与えない
正規表現オブジェクトは引数なので変更されたりしないのか、lastIndexも気にしなくていいぽい
つまりローカル正規表現とグローバル正規表現を使い分ける意味はなさそう
マッチしなかった場合は-1が返ってくる
// ローカル正規表現
const regexp13 = /moko(\d+)/ // 敢えてキャプチャを指定しています
"moko moko456".search(regexp13)
// => 5
"moko moko456".search(regexp13)
// => 5 <== 何度実行しても戻り値は変わらない
"".search(regexp13)
// => -1
// グローバル正規表現時
const regexp14 = /moko(\d+)/g // 敢えてキャプチャを指定しています
"moko moko456".search(regexp14)
// => 5
"moko123 moko456".search(regexp14)
// => 5 <== 何度実行しても戻り値は変わらない
"".search(regexp14)
// => -1
string#search(regexp) ではグローバル正規表現を使うメリットは無いぞ!
string#split(regexp)
マッチした箇所で文字列を分割して配列を返す
戻り値にキャプチャは影響を与えな・・与える。ていうか予想外の動作になるので注意
正規表現オブジェクトは引数なので変更されたりしないのか、lastIndexも気にしなくていいぽい
つまりローカル正規表現とグローバル正規表現を使い分ける意味はなさそう
// ローカル正規表現
const regexp15 = /\d+/ // キャプチャなし
"a1b23c4d".split(regexp15)
// => ["a", "b", "c", "d"]
"a1b23c4d".split(regexp15)
// => ["a", "b", "c", "d"] <== 何度実行しても戻り値は変わらない
// ローカル正規表現
const regexp16 = /(\d+)/ // キャプチャ
"a1b23c4d".split(regexp16)
// => ["a", "1", "b", "3", "c", "4", "d"] <== えっ
"a1b23c4d".split(regexp16)
// => ["a", "1", "b", "3", "c", "4", "d"] <== 何度実行しても戻り値は変わらない
// ローカル正規表現
const regexp17 = /(?:\d+)/ // キャプチャ無効
"a1b23c4d".split(regexp17)
// => ["a", "b", "c", "d"]
"a1b23c4d".split(regexp17)
// => ["a", "b", "c", "d"] <== 何度実行しても戻り値は変わらない
// グローバル正規表現
const regexp18 = /\d+/g // キャプチャなし
"a1b23c4d".split(regexp18)
// => ["a", "b", "c", "d"]
"a1b23c4d".split(regexp18)
// => ["a", "b", "c", "d"] <== 何度実行しても戻り値は変わらない
// グローバル正規表現
const regexp19 = /(\d+)/g // キャプチャ
"a1b23c4d".split(regexp19)
// => ["a", "1", "b", "3", "c", "4", "d"] <== えっ
"a1b23c4d".split(regexp19)
// => ["a", "1", "b", "3", "c", "4", "d"] <== 何度実行しても戻り値は変わらない
// グローバル正規表現
const regexp20 = /(?:\d+)/g // キャプチャ無効
"a1b23c4d".split(regexp20)
// => ["a", "b", "c", "d"]
"a1b23c4d".split(regexp20)
// => ["a", "b", "c", "d"] <== 何度実行しても戻り値は変わらない
string#split(regexp) ではグローバル正規表現を使うメリットは無いぞ!
string#split(regexp) ではキャプチャは使うな!
string#replace(regexp, string)
文字列にマッチした箇所を置換する
ローカル正規表現時は最初にマッチした箇所のみ、グローバル正規表現字は全てのマッチした箇所が対象
戻り値にキャプチャは影響を与えない
正規表現オブジェクトは引数なので変更されたりしないのか、lastIndexも気にしなくていいぽい
// ローカル正規表現
const regexp21 = /moko\d+/
"moko123 moko456".replace(regexp21, "hage")
// => "hage moko456"
"moko123 moko456".replace(regexp21, "hage")
// => "hage moko456" <== 何度実行しても戻り値は変わらない
// グローバル正規表現
const regexp22 = /moko\d+/g
"moko123 moko456".replace(regexp22, "hage")
// => "hage hage"
"moko123 moko456".replace(regexp22, "hage")
// => "hage hage" <== 何度実行しても戻り値は変わらない
オマケ 後方参照系を利用して置換してみる
$1, $2 はキャプチャ箇所がそれぞれに置換されるんでしたね
// ローカル正規表現
const regexp23 = /moko([13])/
"moko1 moko2 moko3".replace(regexp23, "hage$1")
// => "hage1 moko2 moko3"
"moko1 moko2 moko3".replace(regexp23, "hage$1")
// => "hage1 moko2 moko3" <== 何度実行しても戻り値は変わらない
// グローバル正規表現
const regexp24 = /moko([13])/g
"moko1 moko2 moko3".replace(regexp24, "hage$1")
// => "hage1 moko2 hage3"
"moko1 moko2 moko3".replace(regexp24, "hage$1")
// => "hage1 moko2 hage3" <== 何度実行しても戻り値は変わらない
$& はマッチ箇所全体に置換されるんでしたね
// ローカル正規表現
const regexp25 = /moko[13]/
"moko1 moko2 moko3".replace(regexp25, "hage_$&")
// => "hage_moko1 moko2 moko3"
"moko1 moko2 moko3".replace(regexp25, "hage_$&")
// => "hage_moko1 moko2 moko3" <== 何度実行しても戻り値は変わらない
// グローバル正規表現
const regexp26 = /moko[13]/g
"moko1 moko2 moko3".replace(regexp26, "hage_$&")
// => "hage_moko1 moko2 hage_moko3"
"moko1 moko2 moko3".replace(regexp26, "hage_$&")
// => "hage_moko1 moko2 hage_moko3" <== 何度実行しても戻り値は変わらない
string#replace(regexp) ではローカル正規表現もグローバル正規表現も、両方便利なものだ!
string#replace(regexp, function(f, s, s+1, ..., n-2, n-1, n) {})
string#replace()は第2引数に functionも受け付ける
つまり、コールバック関数の戻り値を利用して置換するということ
コールバック関数の引数は可変長になる
- f マッチした文字列
- s キャプチャ その1
- s+1 キャプチャ その2
- n-2 キャプチャ 最後
- n-1 マッチ位置
- n 検索対象文字列全て
// ローカル正規表現
const regexp27 = /(moko)(\d+)/
"moko123 moko456".replace(regexp27, function() {
console.log(arguments);
return arguments[1] + (parseInt(arguments[2], 10) * 2);
})
// => ["moko123", "moko", "123", 0, "moko123 moko456"]
// => "moko246 moko456"
// グローバル正規表現
const regexp28 = /(moko)(\d+)/g
"moko123 moko456".replace(regexp28, function() {
console.log(arguments);
return arguments[1] + (parseInt(arguments[2], 10) * 2);
})
// => ["moko123", "moko", "123", 0, "moko123 moko456"]
// => ["moko456", "moko", "456", 8, "moko123 moko456"]
// => "moko246 moko912"