JavaScript正規表現メモ。
タイトル変えました。旧タイトル「JavaScriptでよく使う書き方」。よく使うけど毎回忘れる。
正規表現にマッチするかどうか。
RegExp#testを使う。
/abc/.test("abcdefg") // => true
String#searchはマッチした位置を返す。マッチしない場合は-1。先頭にマッチすると0でfalseなので注意。真偽値が欲しい場合はString#searchを使わない。
"abcdefg".search(/xxx/) // => -1 "abcdefg".search(/def/) // => 3 "abcdefg".search(/abc/) == false // => true
正規表現の部分マッチを得る。
部分マッチを得るには、RegExp#execかString#matchを使う。execとmatchの速度は大して変わらない。
部分マッチを得るイディオム。マッチしなかったらundefined。
var m = (/ab(c+)/.exec("abcccd")||[])[1]; var m = ("abcccd".match(/ab(c+)/)||[])[1];
マッチしなかったらnullにしたい場合は、||nullを付ける。
var m = (/ab(c+)/.exec("abcccd")||[])[1]||null; var m = ("abcccd".match(/ab(c+)/)||[])[1]||null;
以下変遷。
マッチすることに確信が持てる場合、または例外がでても構わない場合は、
var m = /ab(c+)/.exec("abcccd")[1]; // => ccc var m = "abcccd".match(/ab(c+)/)[1]; // => ccc
これだと1文で書ける。マッチしないとnullが返るため、[1]で例外が出てしまう。Rubyのto_aみたいにnullを[]に変換できるといいんだけど。
しょうがないので以下のようにする。1文にまとまらない。
var m = /ab(c+)/.exec("abcccd"); m = m && m[1];
標準でないRegExp.$1は使わない。
b:id:dankogai javascript:alert(("abcccd".match(/ab(e+)/)||[])[1]) // はいかが
はてなブックマーク - JavaScript正規表現メモ。 (JavaScriptでよく使う書き方。) - こせきの技術日記
うわあ、、ですよね。。かなり考えたんだけど出てきませんでした。ありがとうございます。というわけで……
1文で部分マッチを返す。
var m = (/ab(c+)/.exec("abcccd")||[])[1]; var m = ("abcccd".match(/ab(c+)/)||[])[1];
マッチしなかった場合の値はnullではなくundefinedになる。
js> [][1] === undefined true js> [][1] === null false
RegExpの挙動に合わせてnullにそろえるとか、、やり過ぎか。
var m = (/ab(c+)/.exec("abcccd")||[null,null])[1]; var m = ("abcccd".match(/ab(c+)/)||[null,null])[1];
b:id:Ooo (/ab(c+)/.exec('abccc') || [])[1] || null; こうかすら?
はてなブックマーク - JavaScript正規表現メモ。 (JavaScriptでよく使う書き方。) - こせきの技術日記
そのほうがずっといいですねー。
全てのマッチした箇所を得る。
gオプションを付けてString#matchを使う。
var m = "abcdefabcdef".match(/abc/g); // => ["abc", "abc"]
マッチでループする。
gオプションを付けてRegExp#execを使う。正規表現は(/.../g)ではなくnew RegExp(/.../g)を使う。
var myRe = new RegExp(/ab*/g); var str = "abbcdefabh"; var myArray; while ((myArray = myRe.exec(str)) != null) { var msg = "myArray[0] + " を見つけました。"; msg += "次のマッチは " + myRe.lastIndex + " からです。" print(msg); }
また、String#replaceの第二引数にfunctionを指定する形式でもループできる。
var rex = new RegExp(/ab(.)/g); var replaced = "ab0ab1ab2".replace(rex, function(match0, match1, offset, original) {...})
リテラルをそのまま使わないほうがいい理由は以下のとおり。
Firefoxの正規表現リテラルは、状態を持っている。(なにそれ こわい)
Firefox,Opera,Chromeも。IEやSafariは違うみたい。以下のデモ参照。この記事を書くまで全然知らなかった。
以下も参照。ブックマークコメントも。実用にはならなそうだけど、面白い。
というわけで、以下のルールを守らなければならない。
ループの中にgオプションが付いた正規表現リテラルを書かない。
ループの中でg付きの正規表現をexecすると、無限ループになる可能性がある。
// ブラウザによっては毎回先頭にマッチして無限ループになる。 while(/ab(.)/g.exec("ab0ab1ab2")) { } // これだとどうか?while内でbreak/returnして途中で終わると、リテラルにその状態が残る。次にここを実行すると途中からループが始まる。 // 対象の文字列を引数で取っているfunctionだったりすると非常にマズイ。デモのtest3参照。 var re = /ab(.)/g; while(re.exec("ab0ab1ab2")) { } // new RegExp()が安心。見た目通りに動いてくれる。 var re = new RegExp(/ab(.)/g); while(re.exec("ab0ab1ab2")) { }
同様にfunctionの中にg付き正規表現リテラルを書くと、呼び出し毎に実行結果が変わる可能性がある。ただ、String#matchで使う分には問題無い気がする。
function test() { var m1 = /ab(.)/g.exec("ab0ab1ab2"); // Firefoxだとm1の値が呼び出し毎に変わる。 => 0 1 2 var m2 = "ab0ab1ab2".match(/ab(.)/g); // これなら大丈夫。 => [0,1,2] }
new RegExp()の引数には正規表現リテラルを使う。
new RegExp("\\s+", "g")
よりも
new RegExp(/\s+/g)
の方がよい。エスケープが減る。コメントでjserさんに教えてもらった。
EcmaScript仕様で、RegExpの第1引数に正規表現リテラルを取れることが書いてある。その場合、第2引数が指定されるとTypeErrorになる。
文字列を分割する。
String#splitが使えるが、引数に正規表現を使ってはいけない。正規表現を指定した場合の挙動はブラウザごとに異なる。特にIEは全然違う。
"abc<br>def<br><br>".split("<br>") // => ["abc", "def", "", ""] "abc<br>def<br><br>".split(/<br>/) // => ["abc", "def", "", ""] Firefox 3 // => ["abc", "def"] IE6
IE6でsplitに正規表現を指定すると、分割した要素の中から空文字列の要素を消してしまう。
正規表現を使いたい場合、クロスブラウザなsplitが以下に掲載されているのでこれを使う。
splitの挙動を詳細に調べているテストが以下にある。
IE6,Firefox3,Safari3,Opera9,Chrome1,Chrome2で試した。
- Opera9(Mac) 満点
- Chrome1(Win) 満点
- Chrome2(Win) 3個失敗 増えた?
- Safari3(Mac) 7個失敗
- Firefox3(Mac) 7個失敗
- IE6(Win) 20個失敗
文字を置き換える。
trはない。通常はString#replaceを使う。
- 404 Blog Not Found:javascript - String.prototype.tr() released trの実装。行数を高速に数える話。
- 行数の数え方: Days on the Moon splitしても遅くないという話。テスト対象の文字列は小さい。
- 文字列処理の性能に関する考察 - blanket log サイズによる影響。splitしても部分文字列がメモリを浪費することはない。
正規表現ライブラリ
XRegExp
- JavaScript Regex :: XRegExp
- JavaScriptの正規表現をパワーアップ!「XRegExp」|オープンソース・ソフトウェア、ITニュースを毎日紹介するエンジニア、デザイナー向けブログ
- XRegExp("pattern", "x");
- /pattern/.addFlags("s");
- 作者は上のクロスブラウザsplitを作っている方。Steven Levithanさん。
- sフラグ …… .に何でもマッチする。
- xフラグ …… 複数行・コメントあり。
- 名前付きキャプチャ。
- split他いくつかのクロスブラウザ対応を自動的に行う。
- http://xregexp.com/cross_browser/ 正規表現のブラウザ非互換についての、たぶん世界で一番詳しい記事。
- 自前のフラグやエスケープシーケンスを簡単に足せる。
// \\sのようにバックスラッシュは2つ書く必要がある。 // 第一引数にリテラルも渡せるがflagは指定できない。 var regex = XRegExp("(?<month> [0-9]+ ) [-/.\\s] # month\n" + "(?<day> [0-9]+ ) [-/.\\s] # day \n" + "(?<year> [0-9]+ ) # year ", "x"); // 名前付きキャプチャ var match = regex.exec("04/20/2009"); match.month; // -> 04 // 名前で後方参照して置換 var input = "04/20/2009"; var output = input.replace(regex, "${year}-${month}-${day}"); // -> "2009-04-20"
addFlagでx,sフラグを追加できる。この場合はリテラルが使える。
// This causes the regex literal's source to be recompiled as a new XRegExp var regex = /\w.\w/.addFlags("gs");
バックラッシュで可読性が落ちるのがつらい。個人的にs,x,named captureはそれほど欲しいと思ったことがない。クロスブラウザ対応は気になる。
サンプルのJavaScriptをブラウザで実行してみる。
昔つくったRealtime Eveluator。久々に触ったら楽しかったので、良かったら試してみてください。
- Realtime JavaScript Evaluator
- evalにチェックを入れるとソースを書き換えながらリアルタイムに結果を表示できる。
- only onceは一回だけ実行。evalをチェックしていない時用。
- print()が使える。
この記事は github で履歴を参照できます。