uhyohyo.net

JavaScript初級者から中級者になろう

十六章第十九回 ES2015の正規表現

このページの最終更新日:

ES2015の追加機能を網羅しようシリーズは今回で最後です。今回は正規表現を扱います。この講座で正規表現を解説したのは相当昔ですが、やはりとても便利な言語機能のひとつです。そこで、ES2015では正規表現にも機能拡張が行われました。

Unicodeサポート

前回、String#codePointAtなどの、U+10000以上の文字を正しく扱える文字列のメソッドを紹介しました。実は、正規表現も従来はU+10000以上の文字を正しく扱えませんでしたが、ES2015では正しく扱う方法が追加されました。

まず、うまく扱えない例を見ましょう。


var str = "foo😂bar";

console.log(/^foo.bar$/.test(str)); // false

この例において、/^foo.bar$/という正規表現はfooとbarの間に1文字あるような文字列を表します。.は任意の文字ひとつ(ただし改行は除く)を表すのでしたね。変数strの中身はfooとbarの間に😂という文字がある文字列です。しかし、この文字列はこの正規表現にはマッチしません。この理由は、従来の正規表現においてもやはり文字というのは実はコードユニットを指しており、😂という文字はサロゲートペア(すなわちコードユニット2つ)で表されるからです。実際、次のように.を2つ用意してあげるとマッチします。


var str = "foo😂bar";

console.log(/^foo..bar$/.test(str)); // true

しかしやはり、これは直感的ではありませんね。サロゲートペアに関する知識がないと、U+10000以上の文字を含む文字列を正しく扱うのが困難です。

ということで、今回紹介する新機能はuフラグです。uフラグを持つ正規表現は、U+10000以上の文字も1文字として扱います。

これまでに登場したフラグはiとgでしたね。uフラグも同様に正規表現リテラルに書くことができます。


var str = "foo😂bar";

console.log(/^foo.bar$/u.test(str)); // true

uフラグをつけた正規表現を用いることで、.が😂という文字にマッチするようになりました。

また、前回紹介した\u{1f602}のようなエスケープシーケンスは、uフラグありの正規表現の中でも使うことができます。


var str = "foo😂bar";

console.log(/\u{1f602}/u.test(str)); // true

yフラグ

もうひとつ、ES2015ではyフラグという新しいフラグが追加されました。これはパーサーなどを書くときに便利なフラグです。ちなみに、yはstickyのyらしいです。これはさらっと紹介するので、興味がある方はより詳しく調べてみてください。

yフラグを持つ正規表現は、指定した位置からしかマッチしなくなります。位置は、正規表現オブジェクトのlastIndexプロパティで指定します。


var str = "foobar";
var reg = /bar/y;

reg.lastIndex = 0;

console.log(reg.test(str)); // false

reg.lastIndex = 3;

console.log(reg.test(str)); // true

console.log(reg.lastIndex); // 6

この例では"foobar"という文字列に対して/bar/yという正規表現をマッチさせようとしています。この正規表現は文字列の3文字目からマッチすることができますが、最初はreg.lastIndexを0にセットしているためマッチに失敗します。次にreg.lastIndexを3に変えると、マッチが成功するようになります。

また、yフラグを持つ正規表現がマッチに成功したとき、lastIndexが書き換えられるという特徴があります。このときlastIndexはマッチした文字列の長さ分だけ進みます。今回の場合、文字列の3文字目から"bar"という3文字にマッチしたので、その分だけlastIndexが進み6となります。

well-knownシンボルによるカスタマイズ

ここからは、もはやおなじみのwell-knownシンボルの話です。正規表現に関連するwell-knownシンボルは4つもあります。これらのシンボルにより正規表現関連の動作をカスタマイズすることができます。

最初は@@matchです。名前が示すように、これを使うとString#matchメソッドの動作をカスタマイズできます。matchメソッドに、正規表現の代わりに@@matchメソッドを持つようなオブジェクトを渡すと、その@@matchメソッドが呼ばれます。


var myregexp = {
  [Symbol.match](str){
    return 'hello';
  },
};

console.log('foobar'.match(myregexp)); //"hello"

なんだか意味がわかりませんね。なお、正規表現オブジェクトは@@matchメソッドを持っているので、String#matchを使う代わりにそれを使うことも一応可能です。


var reg = /bar/;

console.log(reg[Symbol.match]('foobar'));

他に@@replace, @@search, @@splitというメソッドがwell-knownシンボルがあり、役割は@@matchとほぼ同じです。

これらのwell-knownシンボルはどれも正規表現が関わる文字列のメソッドに対応しています。これらのシンボルは正直、使いみちが全然ありませんが、万一ものすごくカスタマイズした正規表現オブジェクトを作りたくなった場合には役に立つのかもしれません。