Javascript のゼロ幅アサーション、この記事を読むだけで十分です

1. 定義


1.1 アサーションとは何ですか?

アサーションは、特定の条件を満たすべき何か、または何かが配置されている場所を見つけるために使用されます。

では、ゼロ幅アサーションとは何でしょうか? ゼロ幅とはどういう意味ですか?

1.2 ゼロ幅アサーション

平たく言えば、幅ゼロのアサーションは文字通り、幅がゼロのアサーションと一致することを意味します。

いわゆるマッチング幅はゼロです。これは、このアサーションが機能する場合、次のことを意味します。

  1. マッチング判定条件としてのみ
  2. 実際にはどの結果とも一致しません
  3. コンテンツをキャプチャしません

これら 3 つの点は、ゼロ幅アサーションの定義を示すだけでなく、ゼロ幅アサーションの 2 つの特性、つまり文字をキャプチャしないことと文字を食べないことも示しています。

これら 2 つの機能については次の章で説明します。


2. ゼロ幅アサーション式とケース

後ろに case コードがいくつかあるため、誰もがコードをより明確に理解できるように、この章では最初にゼロ幅アサーション式について説明します。

2.1 式
表現 名前 説明
(?=exp) 前方 (肯定的な先読み) 先読みアサーション 位置と一致します。位置の次の内容は式 exp と一致する必要があります
(?!経験値) 否定 (否定先読み) 先読みアサーション 位置に一致します。位置の背後にあるコンテンツは式 exp に一致しません。
(?<=exp) ポストフォワード (前方ルックバック) アサーション 位置を一致させます。位置の前のコンテンツは式 exp と一致する必要があります (JS はサポートしていません)。
(?<!exp) 否定後読み (negative lookback) アサーション 位置に一致します。位置の前のコンテンツは式 exp に一致しません (JS はサポートしていません)

この表を見れば十中八九理解できると思いますが、ここでちょっとした例を挙げてみましょう。

2.2 事例分析

さて、次のような要件があります。

()文字列「北京 (朝陽区)(西城区)(海淀区)」から、ラップされていない文字を取り出します。北京市

これを行う方法?

要件は、()ラップされていないフィールドを取り出すことです。その後、フィールドの後に を続ける必要があります(。それは非常に簡単です。

次に、ゼロ幅のアサーション式を作成して、要件を満たせるかどうかをvar reg = /.*?(?=\()/
確認

console.log(reg.exec('北京市(朝阳区)(西城区)(海淀区)')) 
// ["北京市", index: 0, input: "北京市(朝阳区)(西城区)(海淀区)", groups: undefined]
console.log('北京市(朝阳区)(西城区)(海淀区)'.match(reg))
// ["北京市", index: 0, input: "北京市(朝阳区)(西城区)(海淀区)", groups: undefined]

簡単に説明すると、これは 2 つの部分として見るregことができます.*?(?=\()

正規表現についてすでに知っていると仮定します。

  • .改行と行末文字を除く任意の文字との一致を表します
  • *この単語が取得さ北京市れないように、何度でも一致することを表します
  • ?複数の連続する値を表します。0 回一致するか、1 回一致し、最大 1 回一致します。

次に、前半部分は指定された文字に含まれるすべての文字と一致し、ゼロ幅アサーションの後半部分は一致に条件を追加し、一致したコンテンツに従う必要があるため、目的の結果を得ることができます(

2.3 貪欲モードと非貪欲モード

このように書くと/.*(?=\()/、なぜ結果が間違っているのかと疑問に思うかもしれません。

console.log('北京市(朝阳区)(西城区)(海淀区)'.match(/.*(?=\()/)) 
// ["北京市(朝阳区)(西城区)", index: 0, input: "北京市(朝阳区)(西城区)(海淀区)", groups: undefined]

まず、グリーディ モードと非グリーディ モードが追加されていることを説明します。これは、上記のケースは前方 (肯定的予測) 先読みアサーションのケースであり、インターネット上の一部の人々は、前方ゼロ幅アサーションは から実行されると言っているからです。私は批判するつもりはありませんが、皆さんが目を開いていてほしいと願っています。

では、なぜ疑問符がなくなり、まったく異なる結果が得られるのでしょうか?

前述したように、?この式では、一致しないか、最大 1 つの一致を意味します。これは、

を追加しないと?、式は貪欲モードで動作します。貪欲モードとは何ですか? できるだけ一致させ、一致するときは一致させる、貪欲に文字列の最後まで一致させる

string には 3 つあるため(、greedy モードでは文字列の最後まで一致し、北京市(朝阳区)(西城区)この結果が得られます。

ここに画像の説明を挿入



3. ゼロ幅アサーションの特徴

第 1 章でゼロ幅アサーションの定義とその特徴について説明しましたが、ここではその特徴のうち、「非捕捉特性」「非食い込み文字」の 2 つについて詳しく説明します。

3.1 非キャプチャ機能

これはゼロ幅アサーションの重要な機能、つまり非キャプチャ機能です。

正規表現を使用して値をグループ化できることはご存知かと思います()。1 つは()グループです。
そこでここでは、グループ化された値を使用して文字を置換する例を示します

var str = 'Hello, Wrld!';
var reg = /(W)/g;
console.log(str.replace(reg, "$1o"))  // Hello, world!

上記のコードでは、グループ化を使用して文字を取得し、W正しい文字に置き換えています。Hello, world!
次に、コードを変更して、何が起こるかを確認します。

var str = 'Hello, Wrld!';
var reg = /W(?=r)/g;
console.log(str.match(reg)) // ["W"]
// 注意看使用match方法返回了正确的结果,那么我能替换成功吗
console.log(str.replace(reg, "$1o"))  // Hello, $1orld!

明らかに、str.matchこのメソッドは、置換では期待した効果が得られず
、この結果は非キャプチャ グループ化を使用した場合と一致します。

var str = 'Hello, Wrld!';
var reg = /(?:W)/g;
console.log(str.replace(reg, "$1o"))  // Hello, $1orld!

したがって、次のように結論付けます。

2 番目のコード スニペットでも()グループ化が使用されていますが、このグループ化では幅 0 のアサーション式が使用されているため、正規表現はそのグループへの参照を返さないため、$1置換は成功しません。

したがって、この例は、ゼロ幅アサーションに非キャプチャー特性があることの良い証拠となります。

3.2 キャラクターの特徴を食べないでください

ゼロ幅アサーションのもう 1 つの特徴は、文字を食べないことです。

ここでの食べてはいけない文字とは、通常の意味で食べるのではなく、ゼロ幅アサーション自体が検索位置を占有せず、検索対象の内容としてカウントされないという意味です。式はゼロ幅アサーション自体と一致しません。

ここでまずコンセプトを紹介します。

この概念は、 と呼ばれる通常のオブジェクトの属性ですlastIndex正規表現の次の一致の開始位置を指定します。

testなどの通常の方法を使用するとexec、 の影響lastIndex

var reg = /([a-z])/g;
var arr = ["a", "b", "c", "d", "e"];
for(var i = 0; i < arr.length; i++) {
    
    
    console.log(reg.exec(arr[i]))
}

このコードの戻り結果を間違える人が多いと思うので、結果を直下に載せておきます

[“a”, “a”, インデックス: 0, 入力: “a”, グループ: 未定義]
null
[“c”, “c”, インデックス: 0, 入力: “c”, グループ: 未定義]
null
[" e"、"e"、インデックス: 0、入力: "e"、グループ: 未定義]

ショックで顎を落としましたか?

これはlastIndex面白いですね。簡単に言うと、通常のグローバル マッチが成功すると、lastIndex成功したマッチの文字インデックスを指し、再度マッチすると、lastIndexその位置から逆方向にマッチし続けます。

配列内の各項目の長さは 1 であることに注意してください。lastIndexの値が 1 以上の場合、自然な一致結果は になりますnull
一致する結果がない場合、lastIndexの値はデフォルト値の 0 になり、次の一致は null 以外の結果になります。

したがって、lastIndexマッチング結果が変化すると、次のように結論付けられます。

ゼロ幅アサーションを使用して試してみましょう。

var reg = /(?=[a-z])/g;  
var arr = ["a", "b", "c", "d", "e"];
for(var i = 0; i < arr.length; i++) {
    
    
    console.log(reg.exec(arr[i]))
}

この結果を推測できるかどうかはわかりませんが、

[""、インデックス: 0、入力: "a"、グループ: 未定義]
[""、インデックス: 0、入力: "b"、グループ: 未定義]
[""、インデックス: 0、入力: "c"、グループ: 未定義]
[""、インデックス: 0、入力: “d”、グループ: 未定義]
[""、インデックス: 0、入力: “e”、グループ: 未定義]

また、正規表現はゼロ幅アサーション自体と一致しないため、ゼロ幅アサーションはlastIndex値を変更しないため、“”正確な null ではなく、 null を返します。

それでも理解できない場合は、次のコードを見てください。

var str = 'hello, this is hierry, and that is hi, there is handsome'
var reg = /h(?!i)e/
console.log(str.match(reg))

このコードは非常に単純で、結果は 1 つだけと一致します。

[「彼」、インデックス: 0、入力: 「こんにちは、こちらはヒエリー、そしてあれはこんにちは、ハンサムです」、グループ: 未定義]

明らかに、 reg は と を照合するときに式の途中をスキップしi、 と のみhを照合しeます。これは、文字を食べないという特徴をより明確に反映することができます。


4. 4 種類のゼロ幅アサーション

一般に、ゼロ幅アサーションは、アサーション方法に応じて、次の 4 つのカテゴリに分類されます。

  • ゼロ幅前方先読みアサーション (正先読みアサーションとも呼ばれます)
  • ゼロ幅の否定先読みアサーション (否定先読みアサーションとも呼ばれます)
  • ゼロ幅の前方後読みアサーション (正の後読みアサーションとも呼ばれます)
  • ゼロ幅の負の後読みアサート (負の後読みアサートとも呼ばれます)

JavaScript 言語では、事前アサーションのみがサポートされており、事後アサーションはサポートされていません。

4.1 肯定的な主張と否定的な主張の違いは何ですか?

前方アサーションとは、特定の位置の前後のコンテンツが式 expに一致する場合、空ではない結果が返されるというものです。

否定アサーションは、特定の位置の前後のコンテンツが式 exp と一致しない場合、空ではない結果が返されることを示します。

4.2 アサーション前とアサーション後の違いは何ですか?

先読みアサーションは、式 exp を特定の位置以降のコンテンツと照合することです。

ポストアサーションは、式 exp を特定の位置の前のコンテンツと照合することです。

4.2 ゼロ幅前方先読みアサーション

例 1、面接の質問で見つけました

洋服38元、靴62元、散髪15元、食事45元、タクシー30元、アイスクリーム10元、使ったお金を合計すると?

答え:/\d.?\d(?=[元块])/g

var s = '衣服38元、鞋子62元、剪发15元、吃饭45块、打车30元、冰激凌10元'
var r = /\d.?\d(?=[元块])/g
var sum = s.match(r).reduce(function(prev, cur) {
    
    
     return prev*1 + cur*1;
})

console.log(sum);// 200

例 2

「私は食べること、寝ること、映画を見ること、そして」から ing で終わるアクションワードを取得します。

答え:/\w+(?=ing)/g

var s = 'i like eating 、sleeping、 see movies and '
var r = /\w+(?=ing)/g
console.log(s.match(r))   // ["eat", "sleep"]

これら 2 つの例は比較的単純なので、じっくり説明するつもりはありません。

4.2 ゼロ幅の否定先読みアサーション

たとえば、面接の質問にもありました

ファイルに .css 接尾辞が付いているが、.min.css にすることはできないかどうかをテストします (例:
test('a.min.css'); // false
test('b.css'); // true
test('c .mining.css'); // true

答え:/^(?!.*\.min\.css$).+\.css$

ここでは、まず任意の文字で始まり .min.css で終わるファイル名を除外し、残りのファイル名から .css で終わるファイル名を検索します。

var reg=/^(?!.*\.min\.css$).+\.css$/;

console.log(reg.test('a.min.css')); // false
console.log(reg.test('.min.css')); // false
console.log(reg.test('.css')); // false
console.log(reg.test('min.css')); // true
console.log(reg.test('b.css')); // true
console.log(reg.test('a.b.css')); // true
console.log(reg.test('c.mining.css')); // true

5. エピローグ

作成するのは簡単ではありません。離れる前に指を動かして親指を立ててください。

おすすめ

転載: blog.csdn.net/zhai_865327/article/details/119005629