フラグy
を使用すると、ソース文字列の指定された位置で検索を実行できます。
y
フラグのユースケースを理解し、正規表現の使い方をよりよく理解するために、実用的な例を見ていきましょう。
正規表現の一般的なタスクの1つに「字句解析」があります。プログラミング言語などのテキストを取得し、その構造要素を見つける必要があります。たとえば、HTMLにはタグと属性があり、JavaScriptコードには関数、変数などがあります。
字句解析器の記述は、独自のツールとアルゴリズムを持つ特殊な分野であるため、ここでは深く掘り下げませんが、指定された位置で何かを読み取るという一般的なタスクがあります。
たとえば、コード文字列let varName = "value"
があり、位置4
から始まる変数名を読み取る必要があります。
正規表現\w+
を使用して変数名を検索します。実際、JavaScriptの変数名には正確に一致させるために少し複雑な正規表現が必要ですが、ここでは問題ありません。
str.match(/\w+/)
を呼び出すと、行の最初の単語(let
)のみが見つかります。それでは不十分です。- フラグ
g
を追加できます。しかし、str.match(/\w+/g)
を呼び出すと、テキスト内のすべての単語が検索されますが、位置4
の1つの単語が必要なだけです。これも、必要なものではありません。
では、指定された位置で正規表現を正確に検索するにはどうすればよいでしょうか?
メソッドregexp.exec(str)
を使用してみましょう。
フラグg
とy
のないregexp
の場合、このメソッドは最初のマッチのみを検索し、str.match(regexp)
とまったく同じように機能します。
…しかし、フラグg
がある場合、regexp.lastIndex
プロパティに格納されている位置から始まるstr
で検索を実行します。そして、一致が見つかった場合、regexp.lastIndex
を一致の直後のインデックスに設定します。
言い換えると、regexp.lastIndex
は検索の開始点として機能し、各regexp.exec(str)
呼び出しで新しい値(「最後のマッチの後」)にリセットされます。もちろん、フラグg
がある場合のみです。
したがって、regexp.exec(str)
を連続して呼び出すと、マッチが次々と返されます。
このような呼び出しの例を以下に示します。
let str = 'let varName'; // Let's find all words in this string
let regexp = /\w+/g;
alert(regexp.lastIndex); // 0 (initially lastIndex=0)
let word1 = regexp.exec(str);
alert(word1[0]); // let (1st word)
alert(regexp.lastIndex); // 3 (position after the match)
let word2 = regexp.exec(str);
alert(word2[0]); // varName (2nd word)
alert(regexp.lastIndex); // 11 (position after the match)
let word3 = regexp.exec(str);
alert(word3); // null (no more matches)
alert(regexp.lastIndex); // 0 (resets at search end)
ループですべてのマッチを取得できます。
let str = 'let varName';
let regexp = /\w+/g;
let result;
while (result = regexp.exec(str)) {
alert( `Found ${result[0]} at position ${result.index}` );
// Found let at position 0, then
// Found varName at position 4
}
このようなregexp.exec
の使い方は、プロセスをより詳細に制御できる、str.matchAll
メソッドの代替手段です。
私たちのタスクに戻りましょう。
指定された位置から検索を開始するために、lastIndex
を手動で4
に設定できます!
このように
let str = 'let varName = "value"';
let regexp = /\w+/g; // without flag "g", property lastIndex is ignored
regexp.lastIndex = 4;
let word = regexp.exec(str);
alert(word); // varName
やった!問題解決!
位置regexp.lastIndex = 4
から始まる\w+
の検索を実行しました。
結果は正しいです。
…しかし、待ってください、そんなに速くはありません。
注意してください。regexp.exec
呼び出しは、lastIndex
の位置で検索を開始し、その後続行します。lastIndex
の位置に単語がないが、その後に存在する場合は、見つかるでしょう。
let str = 'let varName = "value"';
let regexp = /\w+/g;
// start the search from position 3
regexp.lastIndex = 3;
let word = regexp.exec(str);
// found the match at position 4
alert(word[0]); // varName
alert(word.index); // 4
字句解析を含む一部のタスクでは、これは間違っています。テキストの指定された位置で正確にマッチを見つける必要があります。そのためにフラグy
があります。
フラグy
は、regexp.exec
が「〜から始まる」のではなく、lastIndex
の位置で正確に検索するようにします。
フラグy
を使った同じ検索を以下に示します。
let str = 'let varName = "value"';
let regexp = /\w+/y;
regexp.lastIndex = 3;
alert( regexp.exec(str) ); // null (there's a space at position 3, not a word)
regexp.lastIndex = 4;
alert( regexp.exec(str) ); // varName (word at position 4)
ご覧のとおり、正規表現/\w+/y
は位置3
では一致しませんが(フラグg
とは異なり)、位置4
では一致します。
必要なものだけでなく、フラグy
を使用すると、パフォーマンスが大幅に向上します。
長いテキストがあり、まったく一致がないと想像してみてください。フラグg
を使った検索はテキストの最後まで進んで何も見つからず、これはフラグy
を使った検索(正確な位置のみを確認する)よりもはるかに時間がかかります。
字句解析などのタスクでは、通常、正確な位置で多くの検索が行われ、そこで何が存在するかを確認します。フラグy
を使用することは、正しい実装と良好なパフォーマンスの鍵となります。
コメント
<code>
タグを使用し、複数行の場合は<pre>
タグで囲み、10行を超える場合は、サンドボックス(plnkr、jsbin、codepen…)を使用してください。