Hatena::ブログ(Diary)

latest log このページをアンテナに追加 RSSフィード

2009-07-02

グローバルフラグ付きの RegExp.test が予想外の動きをする

正規表現が沢山あると、書くほうも読むほうもしんどいので、時々まとめちゃいます。

たとえば、こんなふうに。

function hoge(ary) {
  var i = 0, iz = ary.length, val;
  var rex = /\s*\!\s*important\s*/g; // グローバルフラグ付き

  for (; i < iz; ++i) {
    val = ary[i];

    if (rex.test(val)) { // (2) ここでも流用
      val = val.replace(rex, ""); // (1) ここ向けの正規表現を
      ...
    } else if (/!/.test(val)) {
      ...
    }
  }
}

んで法則が発動したと

<!doctype html><html><head><title></title></head><body>
<script>
function test(route) {
  var ary = [
    "background-color: pink !important",
    "color: red !important",
    "background-color: skyblue !important"
  ];
  var rex  = /\s*\!\s*important\s*/g;
  var rex2 = /\s*\!\s*important\s*/;
  var i = 0, iz = ary.length;
  var val;
  var result = [];

  for (; i < iz; ++i) {
    val = ary[i];

    switch (route) {
    case 0:
      if (rex.test(val)) { // global
        val = val.replace(rex, "");
        result.push(1);
      } else if (/!/.test(val)) {
        result.push(2);
      } else {
        result.push(3);
      }
      break;

    case 1:
      if (/\s*\!\s*important\s*/g.test(val)) { // global
        val = val.replace(rex, "");
        result.push(1);
      } else if (/!/.test(val)) {
        result.push(2);
      } else {
        result.push(3);
      }
      break;

    case 2:
      if (/\s*\!\s*important\s*/.test(val)) { // not global
        val = val.replace(rex, "");
        result.push(1);
      } else if (/!/.test(val)) {
        result.push(2);
      } else {
        result.push(3);
      }
      break;

    case 3:
      if (rex2.test(val)) { // not global
        val = val.replace(rex2, ""); // not global
        result.push(1);
      } else if (/!/.test(val)) {
        result.push(2);
      } else {
        result.push(3);
      }
    }
  }
  return result.join(" > ");
}

alert([test(0), test(1), test(2), test(3)].join("\n"));

</script>
</body></html>

ブラウザ毎の実行結果はこうなります。SafariIE, OperaFirefox が同じ結果になるのって非常に珍しい組み合わせですね。

Chrome 1 > 2 > 1
1 > 2 > 1
1 > 1 > 1
1 > 1 > 1
Safari 1 > 2 > 1
1 > 1 > 1
1 > 1 > 1
1 > 1 > 1
IE 1 > 2 > 1
1 > 1 > 1
1 > 1 > 1
1 > 1 > 1
Firefox 1 > 1 > 1
1 > 2 > 1
1 > 1 > 1
1 > 1 > 1
Opera 1 > 1 > 1
1 > 2 > 1
1 > 1 > 1
1 > 1 > 1

/omatome/g.test(bugbug) は実装依存なので、使っちゃだめなんですね。

ECMAScript3 では、

15.10.6.3 RegExp.prototype.test(string)

RegExp.prototype.exec(string) != null と同様である。

とあるのですが、いったいどのブラウザの動きが正解なんでしょう? 良くわかりません。


そういえば、javascripter さんが…

http://d.hatena.ne.jp/javascripter/20090113/1231863436 で面白い使い方してたの思い出しました。

/omatome/g.test(hoge) の後に、 hoge.replace(/omatome/g, "") すると法則発動です。
/omatome/g.test(hoge) だけなら、lastIndex などに気をつければ大丈夫です。