読者です 読者をやめる 読者になる 読者になる

JavaScript正規表現メモ。

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を使う。

正規表現ライブラリ

XRegExp
// \\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 で履歴を参照できます。