グローバルフラグ付きのRegExpが誤った結果を返すのはなぜですか?


277

グローバルフラグと大文字と小文字を区別しないフラグを使用する場合、この正規表現の問題は何ですか?クエリはユーザーが生成した入力です。結果は[true、true]になります。

var query = 'Foo B';
var re = new RegExp(query, 'gi');
var result = [];
result.push(re.test('Foo Bar'));
result.push(re.test('Foo Bar'));
// result will be [true, false]

var reg = /^a$/g;
for(i = 0; i++ < 10;)
   console.log(reg.test("a"));


54
JavaScriptにおけるRegExpの多くのトラップの1つへようこそ。これは、私がこれまでに出会った正規表現処理への最悪のインターフェースの1つであり、奇妙な副作用と曖昧な警告に満ちています。通常、正規表現で実行する必要がある一般的なタスクのほとんどは、正確に綴ることが困難です。
ボビンス、2009年

XRegExpは良い代替手段のように見えます。xregexp.com
頃の

ここでは、同様の回答を参照してください。stackoverflow.com/questions/604860/...
Prestaul

解決策の1つは、それを回避できる場合は、正規表現リテラルをに保存する代わりに直接使用することreです。
thdoan 2018年

回答:


350

RegExpオブジェクトは、を追跡しますlastIndexので、その後の試合にではなく0のを見てみましょう、最後に使用されたインデックスから開始し、一致が発生した場所:

var query = 'Foo B';
var re = new RegExp(query, 'gi');
var result = [];
result.push(re.test('Foo Bar'));

alert(re.lastIndex);

result.push(re.test('Foo Bar'));

lastIndexすべてのテストの後で手動で0にリセットしたくない場合は、gフラグを削除してください。

仕様で規定されているアルゴリズムを次に示します(セクション15.10.6.2)。

RegExp.prototype.exec(string)

正規表現に対して文字列の正規表現一致を実行し、一致の結果を含むArrayオブジェクトを返します。文字列が一致しなかった場合はnull次のように、正規表現パターンの出現を文字列ToString(string)で検索します。

  1. SをToString(string)の値とします。
  2. lengthをSの長さとします。
  3. lastIndexをlastIndexプロパティの値とします。
  4. ToInteger(lastIndex)の値をiとします。
  5. グローバルプロパティがfalseの場合、i = 0とします。
  6. I <0またはI>長さの場合、lastIndexを0に設定し、nullを返します。
  7. 引数Sとiを指定して、[[Match]]を呼び出します。[[Match]]が失敗を返した場合は、手順8に進みます。それ以外の場合は、rをその状態結果とし、ステップ10に進みます。
  8. i = i + 1とします。
  9. 手順6に進みます。
  10. eをrのendIndex値とします。
  11. グローバルプロパティがtrueの場合、lastIndexをeに設定します。
  12. nをrのキャプチャ配列の長さとします。(これは15.10.2.1のNCapturingParensと同じ値です。)
  13. 次のプロパティを持つ新しい配列を返します。
    • indexプロパティは、完全な文字列S内の一致した部分文字列の位置に設定されます。
    • 入力プロパティはSに設定されます。
    • lengthプロパティはn + 1に設定されています。
    • 0プロパティは、一致した部分文字列に設定されます(つまり、オフセットiを含み、オフセットeを含まないSの部分)
    • I> 0およびI≤nである各整数iについて、ToString(i)という名前のプロパティをrのキャプチャ配列のi番目の要素に設定します。

83
これは、ここにあるGalaxy API設計のヒッチハイクガイドに似ています。"あなたが陥ったその落とし穴は、あなたがチェックするだけに
煩わしかったの

5
Firefoxのstickyフラグは、あなたが示唆することをまったく行いません。代わりに、正規表現の先頭に^があったかのように機能します。ただし、この^ は文字列の先頭ではなく現在の文字列位置(lastIndex)と一致することを除きます。正規表現が「lastIndexの後の任意の場所」ではなく「ここ」に一致するかどうかを効果的にテストしています。あなたが提供したリンクを見てください!
Doin

1
この回答の冒頭の発言は正確ではありません。何も言われていないスペックのステップ3を強調表示しました。の実際の影響lastIndexは、ステップ5、6、11にあります。最初のステートメントは、グローバルフラグが設定されている場合にのみ当てはまります。
Prestaul 2014

@Prestaulはい、それはグローバルフラグについて言及していないことは正しいです。質問の組み立て方が原因で、おそらく(当時、私が何を考えていたかは思い出せません)暗黙的なものでした。回答を自由に編集または削除して、回答にリンクしてください。また、あなたは私よりも優れていると安心させてください。楽しい!
Ionuț G. Stan 2014

@ IonuțG.Stan、申し訳ありませんが、私の以前のコメントが攻撃的であると思われた場合、それは私の意図ではありませんでした。この時点では編集できませんが、コメントの重要なポイントに注意を向けるために、叫ぶつもりはありませんでした。悪い!
Prestaul 2014

72

単一のRegExpオブジェクトを使用し、それを複数回実行しています。連続して実行されるたびに、最後の一致インデックスから続行されます。

各実行の前に最初から開始するには、正規表現を「リセット」する必要があります。

result.push(re.test('Foo Bar'));
re.lastIndex = 0;
result.push(re.test('Foo Bar'));
// result is now [true, true]

毎回新しいRegExpオブジェクトを作成する方が読みやすいかもしれないと言いました(とにかくRegExpがキャッシュされるため、オーバーヘッドは最小限です):

result.push((/Foo B/gi).test(stringA));
result.push((/Foo B/gi).test(stringB));

1
または、単にgフラグを使用しないでください。
メルポメン

36

RegExp.prototype.test正規表現のlastIndexプロパティを更新して、各テストが最後のテストが停止したところから開始されるようにします。プロパティをString.prototype.match更新しないため、使用することをお勧めしlastIndexます。

!!'Foo Bar'.match(re); // -> true
!!'Foo Bar'.match(re); // -> true

注:!!結果をブール値に変換してから反転し、結果を反映します。

または、lastIndexプロパティをリセットすることもできます。

result.push(re.test('Foo Bar'));
re.lastIndex = 0;
result.push(re.test('Foo Bar'));

11

グローバルgフラグを削除すると問題が解決します。

var re = new RegExp(query, 'gi');

する必要があります

var re = new RegExp(query, 'i');

0

re.lastIndex = 0を設定する必要があります。これは、gフラグでregexを実行すると、最後に発生した一致が追跡されるため、re.lastIndex = 0を実行する必要があるため、同じ文字列をテストするためのテストは行われません。

var query = 'Foo B';
var re = new RegExp(query, 'gi');
var result = [];
result.push(re.test('Foo Bar'));
re.lastIndex=0;
result.push(re.test('Foo Bar'));

console.log(result)


-1

/ gフラグを使用すると、ヒット後も検索を続けるように指示されます。

一致が成功した場合、exec()メソッドは配列を返し、正規表現オブジェクトのプロパティを更新します。

最初の検索の前に:

myRegex.lastIndex
//is 0

最初の検索の後

myRegex.lastIndex
//is 8

gを削除すると、exec()が呼び出されるたびに検索が終了します。


OPは使用していませんexec
メルポメン

-1

私は機能を持っていました:

function parseDevName(name) {
  var re = /^([^-]+)-([^-]+)-([^-]+)$/g;
  var match = re.exec(name);
  return match.slice(1,4);
}

var rv = parseDevName("BR-H-01");
rv = parseDevName("BR-H-01");

最初の呼び出しは機能します。2番目の呼び出しはしません。slice操作はNULL値について文句を言います。これはのせいだと思いre.lastIndexます。RegExp関数が呼び出されるたびに新しいものが割り当てられ、関数の複数の呼び出しで共有されないことを期待するため、これは奇妙です。

私がそれを次のように変更したとき:

var re = new RegExp('^([^-]+)-([^-]+)-([^-]+)$', 'g');

すると、lastIndexホールドオーバー効果が出ません。期待どおりに動作します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.