ホームページ  >  記事  >  ウェブフロントエンド  >  jQueryセレクタのソースコード解釈(5):tokenize_jqueryの解析処理

jQueryセレクタのソースコード解釈(5):tokenize_jqueryの解析処理

WBOY
WBOYオリジナル
2016-05-16 16:06:451253ブラウズ

次の分析は、jQuery-1.10.2.js バージョンに基づいています。

以下では $("div:not(.class:contain('span')):eq(3)") を例として、解析を完了するためにトークン化コードと preFilter コードがどのように調整されるかを説明します。 tokenize メソッドと preFilter クラスのコードの各行の詳細な説明を知りたい場合は、次の 2 つの記事を参照してください。

http://www.jb51.net/article/63155.htm
http://www.jb51.net/article/63163.htm

以下は tokenize メソッドのソース コードです。わかりやすくするために、キャッシュ、カンマ マッチング、リレーショナル文字マッチングに関連するコードをすべて削除し、現在の例に関連するコア コードのみを残しています。削除されたコードは非常に単純です。必要に応じて、上記の記事を参照してください。

また、コードは説明文の上に書かれています。

コードをコピー コードは次のとおりです:

関数 tokenize(selector, parseOnly) {
var matched、match、tokens、type、soFar、groups、preFilters;

これまで = セレクター;
グループ = [];
preFilters = Expr.preFilter;

その間 (これまで) {
if (!matched) {
groups.push(トークン = []);
}

一致 = false;

for (Expr.filter に入力) {
If ((match = matchExpr[type].exec(soFar))
&& (!preFilters[タイプ] || (match = preFilters[タイプ]
(一致)))) {
一致 = match.shift();
tokens.push({
値: 一致、
タイプ: タイプ、
一致: match
});
SoFar = soFar.slice(matched.length);
}
}

if (!matched) {
休憩;
}
}

return parseOnly ? soFar.length : soFar.error(selector) :
tokenCache(セレクター、グループ).slice(0);
}


まず、jQuery の実行中に select メソッドによって初めて tokenize が呼び出され、「div:not(.class:contain('span')):eq(3)」がセレクター パラメーターとしてメソッドに渡されます。
コードをコピー コードは次のとおりです:

これまで = セレクター;

これまで = "div:not(.class:contain('span')):eq(3)"
初めて while ループに入ると、matched には値が割り当てられていないため、if 内の次のステートメント本体が実行され、トークン変数が初期化され、トークンがグループ配列にプッシュされます。

コードをコピー コードは次のとおりです:

groups.push(トークン = []);
その後、for文を入力します。
最初の for ループ: Expr.filter から最初の要素「TAG」を取得し、それを type 変数に割り当て、ループ本体のコードを実行します。


コードをコピー コードは次のとおりです:
If ((match = matchExpr[type].exec(soFar))
&& (!preFilters[タイプ] || (match = preFilters[タイプ]
(一致)))) {

match = matchExpr[type].exec(soFar) の実行結果は次のとおりです。

一致 =["div", "div"]

例の最初のセレクターは div で、matchExpr["TAG"] の正規表現に一致しますが、preFilters["TAG"] が存在しないため、if 内のステートメント本体が実行されます。


コードをコピー コードは次のとおりです:
一致 = match.shift();

一致した最初の要素 div を削除し、その要素を一致した変数に代入します。このとき、matched="div", match = ["div"]。

コードをコピー コードは次のとおりです:

tokens.push({
値: 一致、
タイプ: タイプ、
一致: match
}

新しいオブジェクト { value: "div"、type: "TAG"、matches: ["div"] } を作成し、そのオブジェクトをトークン配列にプッシュします。

コードをコピー コードは次のとおりです:

SoFar = soFar.slice(matched.length);

soFar 変数は div を削除します。このとき、soFar=":not(.class:contain('span')):eq(3)"
2 番目の for ループ: Expr.filter から 2 番目の要素「CLASS」を取得し、それを type 変数に割り当て、ループ本体のコードを実行します。

コードをコピーします コードは次のとおりです:

If ((match = matchExpr[type].exec(soFar))
&& (!preFilters[タイプ] || (match = preFilters[タイプ]
(一致)))) {

現在の soFar=":not(.class:contain('span')):eq(3)" は CLASS 型の正規表現と一致しないため、このループは終了します。
3 番目の for ループ: Expr.filter から 3 番目の要素「ATTR」を取得し、それを type 変数に割り当て、ループ本体のコードを実行します。
同様に、現在残っているセレクターは属性セレクターではないため、このサイクルは終了します。

4 番目の for ループ: Expr.filter から 4 番目の要素「CHILD」を取得し、それを type 変数に割り当て、ループ本体のコードを実行します。
同様に、現在残っているセレクターは CHILD セレクターではないため、このサイクルは終了します。

5 番目の for ループ: Expr.filter から 5 番目の要素「PSEUDO」を取得し、それを type 変数に割り当て、ループ本体のコードを実行します。

コードをコピー コードは次のとおりです:

If ((match = matchExpr[type].exec(soFar))
&& (!preFilters[タイプ] || (match = preFilters[タイプ]
(一致)))) {

match = matchExpr[type].exec(soFar) の実行結果は次のとおりです。
[":not(.class:contain('span')):eq(3)", "not", ".class:contain('span')):eq(3", 未定義, 未定義, 未定義, 未定義、未定義、未定義、未定義、未定義]

preFilters["PSEUDO"] が存在するため、次のコードが実行されます:

コードをコピー コードは次のとおりです:

match = preFilters[type](match)

preFilters["PSEUDO"] コードは次のとおりです:

コードをコピーします コードは次のとおりです:

"PSEUDO" : function(match) {
var extra、引用符なし = !match[5] && match[2];

if (matchExpr["CHILD"].test(match[0])) {
null を返す;
}

if (match[3] && match[4] !== 未定義) {
一致[2] = 一致[4];
} else if (引用符なし
&& rpseudo.test(引用符なし)
&& (excess = tokenize(unquoted, true))
&& (過剰 = unquoted.indexOf(")", unquoted.length
- 過剰)
- unquoted.length)) {

match[0] = match[0].slice(0, 過剰);
match[2] = unquoted.slice(0, 過剰);
}

return match.slice(0, 3);
}

渡された一致パラメータは次と同じです:

コードをコピー コードは次のとおりです:

[":not(.class:contain('span')):eq(3)", "not", ".class:contain('span')):eq(3", 未定義, 未定義, 未定義, 未定義、未定義

コードをコピーします コードは次のとおりです:

引用符なし = !match[5] && match[2]

引用なし = ".class:contain('span')):eq(3"

コードをコピーします コードは次のとおりです:

if (matchExpr["CHILD"].test(match[0])) {
null を返します。 }

match[0] = ":not(.class:contain('span')):eq(3)"、matchExpr["CHILD"] 正規表現に一致せず、return null ステートメントは実行されません。


コードをコピー コードは次のとおりです:
if (match[3] && match[4] !== 未定義) {
一致[2] = 一致[4]
; }

match[3] と match[4] はどちらも未定義に等しいため、else 文本体が実行されます。

コードをコピーします コードは次のとおりです:
else if (引用符なし
&& rpseudo.test(unquoted)
&& (excess = tokenize(unquoted, true))
&& (超過 = unquoted.indexOf(")", unquoted.length - 超過) - unquoted.length)

このとき、unquoted = ".class:contain('span')):eq(3" が true となり、unquoted contains:contain('span') が正規表現 rpseudo に一致するので、rpseudo となります。 test(unquoted) が true の場合、次のように tokenize を再度呼び出して、引用符なしで再度解析します。


extra = tokenize(unquoted, true)


今回 tokenize 関数を呼び出すとき、受信セレクター パラメーターは ".class:contain('span')):eq(3" に等しく、parseOnly は true に等しくなります。関数本体の実行プロセスは次のとおりです。以下のように:

コードをコピーします コードは次のとおりです:
これまで = セレクター


これまで = ".class:contain('span')):eq(3" 初めて while ループに入ると、matched には値が割り当てられていないため、if 内の次のステートメント本体が実行され、トークン変数が初期化され、トークンがグループ配列にプッシュされます。



groups.push(トークン = []);


の後にfor文を入力します。
最初の for ループ: Expr.filter から最初の要素「TAG」を取得し、それを type 変数に割り当て、ループ本体のコードを実行します。


コードをコピー コードは次のとおりです: if ((match = matchExpr[type].exec(soFar))
&& (!preFilters[type] || (match = preFilters[type]
(一致)))) {



現在残っているセレクターはTAGセレクターではないため、このサイクルは終了します。
2 番目の for ループ: Expr.filter から 2 番目の要素「CLASS」を取得し、それを type 変数に割り当て、ループ本体のコードを実行します。

match = matchExpr[type].exec(soFar) の実行結果は次のとおりです。

一致 = ["クラス" , "クラス"]

preFilters["CLASS"]が存在しないため、if内の文本体が実行されます。


コードをコピー コードは次のとおりです: 一致 = match.shift();

match の最初の要素クラスを削除し、その要素を matched="class", match = ["class"]
に代入します。

コードをコピーします

コードは次のとおりです: tokens.push({ 値: 一致、
タイプ: タイプ、
一致 : 一致
}

新しいオブジェクト { value: "class"、type: "CLASS"、matches: ["class"] } を作成し、そのオブジェクトをトークン配列にプッシュします。

コードをコピー コードは次のとおりです:

soFar = soFar.slice(matched.length);
このとき、soFar 変数はクラスを削除します。soFar = ":contain('span')):eq(3"

3 番目の for ループ: Expr.filter から 3 番目の要素「ATTR」を取得し、それを type 変数に割り当て、ループ本体のコードを実行します。
同様に、現在残っているセレクターは属性セレクターではないため、このサイクルは終了します。

4 番目の for ループ: Expr.filter から 4 番目の要素「CHILD」を取得し、それを type 変数に割り当て、ループ本体のコードを実行します。

同様に、現在残っているセレクターは CHILD セレクターではないため、このサイクルは終了します。

5 番目の for ループ: Expr.filter から 5 番目の要素「PSEUDO」を取得し、それを type 変数に割り当て、ループ本体のコードを実行します。

コードをコピーします コードは次のとおりです:
if ((match = matchExpr[type].exec(soFar))
&& (!preFilters[type] || (match = preFilters[type]
(一致)))) {

match = matchExpr[type].exec(soFar) の実行結果は以下の通りです:

[":contain('span')", "contain", "'span'", "'", "span", 未定義, 未定義, 未定義, 未定義, 未定義, 未定義]

preFilters["PSEUDO"] が存在するため、次のコードが実行されます:


コードをコピー コードは次のとおりです:
match = preFilters[type](match)

preFilters["PSEUDO"] コードは上に示されているため、ここではリストされません。

コードをコピーします コードは次のとおりです:
"PSEUDO" : function(match) {
var 過剰、引用符なし = !match[5] && match[2];
If (matchExpr["CHILD"].test(match[0])) {
null を返す; }

If (一致[3] && 一致[4] !== 未定義) {
一致[2] = 一致[4];
} else if (引用符なし
                                                                                                                                                                                                      && (excess = tokenize(unquoted, true)) && (excess = unquoted.indexOf(")", unquoted.length
- 過剰)
- unquoted.length)) {

match[0] = match[0].slice(0, 過剰)
match[2] = unquoted.slice(0, 過剰)
}

match.slice(0, 3) を返します。 }



受信一致パラメータは次と同じです:
[":contain('span')", "contain", "'span'", "'", "span", 未定義, 未定義, 未定義, 未定義, 未定義, 未定義]


コードをコピーします

コードは次のとおりです: 引用符なし = !match[5] && match[2]; 引用符なし = "スパン"


コードをコピー


コードは次のとおりです:

if (matchExpr["CHILD"].test(match[0])) { null を返す; }

「:contain('span')」は matchExpr["CHILD"] 正規表現と一致しないため、内部ステートメント本体は実行されません。

コードをコピー コードは次のとおりです:

if (一致[3] && 一致[4] !== 未定義) {
一致[2] = 一致[4];
}

match[3] = "'" かつ match[4] ="span" なので、内部の if 文本体が実行され、match[2] に "span" が代入されます

コードをコピーします コードは次のとおりです:

match.slice(0, 3) を返します。
一致の最初の 3 つの要素のコピーを返します

このとき、tokenizeメソッドのforループに戻って実行を継続します。このときの各変数の値は以下の通りです。
一致 = [":contain('span')", "contain", "span"]

これまで = ":contain('span')):eq(3"

コードをコピーします コードは次のとおりです:
一致 = match.shift();


一致配列から「:contain('span')」を削除し、一致した変数に割り当てます

コードをコピーします コードは次のとおりです: tokens.push({
値: 一致、
タイプ: タイプ、
一致 : 一致
}



新しいオブジェクトを作成します { 値: ":contain('span')"、type:"PSEUDO"、一致: ["contain", "span"] }、オブジェクトをトークン配列にプッシュします。




コードをコピー コードは次のとおりです: soFar = soFar.slice(matched.length);

soFar変数は「:contain('span')」を削除し、その後、forループが終了して再度whileループが実行されるまで、soFar="):eq(3)"となります。有効なセレクターがないため、while ループを終了します。


コードをコピー

コードは次のとおりです: parseOnly を返す : soFar.error(セレクター) : tokenCache(セレクター、グループ).slice(0);

このとき parseOnly = true なので、この時点の soFar の長さ 6 が返され、preFilters["PSEUDO"] のコードが実行され続けます

コードをコピー

コードは次のとおりです: else if (引用符なし && rpseudo.test(unquoted) && (excess = tokenize(unquoted, true))
&& (超過 = unquoted.indexOf(")", unquoted.length - 超過) - unquoted.length)



余分な変数に 6 を代入し、コード

コードをコピー

計算: セレクターの終了位置 (つまり、右括弧の位置) ではありません 22

コードをコピー

完全な :not セレクター文字列 (match[0]) とその括弧内の文字列 (match[2]) をそれぞれ計算します。これらは次と同等です:

match[0] = ":not(.class:contain('span'))"

match[2] = ".class:contain('span')"

コードをコピー


コードは次のとおりです:

match.slice(0, 3) を返します。

一致する最初の 3 つの要素のコピーを返します。
トークン化関数に戻り、match = [":not(.class:contain('span'))", "not", ".class:contain('span')"]

コードをコピー コードは次のとおりです:

一致 = match.shift();
match の最初の要素「:not(.class:contain('span'))」を削除し、その要素を一致した変数に代入します。このとき、matched="":not(.class:contain( ')スパン'))"",

match = ["not", ".class:contain('span')"]

コードをコピー コードは次のとおりです:
tokens.push({
値: 一致、
タイプ: タイプ、
一致 : 一致
}

新しいオブジェクトを作成します { 値: ":not(.class:contain('span'))""、タイプ: "PSEUDO"、一致: ["not", ".class:contain('span') "] } を実行し、オブジェクトをトークン配列にプッシュします。このとき、トークンには 2 つの要素があります。つまり、selector ではなく div です。


コードをコピー コードは次のとおりです:
soFar = soFar.slice(matched.length);

SoFar変数は「:not(.class:contain('span'))」を削除します。このとき、soFar=":eq(3)"は、このforループを終了した後、再びwhileループに戻ります。同様に、トークンの 3 番目の要素の eq セレクターを取得するプロセスは not と一致するため、ここでは詳しく説明しません。最終グループの結果は以下の通りです: group[0][0] = {値: "div"、タイプ: "TAG"、一致: ["div"] }


group[0][1] = {値: ":not(.class:contain('span'))"、タイプ: "PSEUDO"、一致: ["not"、".class:contain('スパン')"] }

グループ[0][2] = {値: ":eq(3)"、タイプ: "PSEUDO"、一致: ["eq", "3"] }


parseOnly を返す : soFar.error(セレクター) :
tokenCache(セレクター、グループ).slice(0);


parseOnly = 未定義のため、tokenCache(selector, groups).slice(0) が実行され、グループがキャッシュにプッシュされ、そのコピーが返されます。
これで、すべての解析が完了したのではないかと疑問に思う人もいるかもしれません。はい、実際の操作ではこれを再度解析する必要があります。もちろん、「class:contain('span')):eq(3」を解析したときに、有効なセレクターの結果をキャッシュに保存できれば、再度解析する必要がなくなり、実行速度が向上します。実行中に「.class:contain('span')」が分析のために再度送信されると、キャッシュに保存されるため、現在の実行速度が向上するだけです。 この時点で、実行プロセス全体が終了しました。


声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。