第二章 jQuery技術解密 (六)

mybwu_com發表於2013-12-28

2.4 解析 jQuery 選擇器引擎 Sizzle

jQuery 從 1.3 版本開始,使用了新的選擇器引擎 Sizzle(官方網址 http://sizzlejs.com) 。Sizzle 是 jQuery 作者 John Resig 開發的 DOM 選擇器引擎 (Dom Selector Engine),速度號稱業界第一。而且它有一個重要的特點就是 Sizzle 是完全獨立於 jQuery 的,如果使用者不想用 jQuery ,還可以只用 Sizzle 。

Sizzle 選擇器引擎目前成為 jQuery 框架預設的選擇器引擎,相比原來的 jQuery 引擎,速度有很大的提升,如圖 2.3 所示的各種選擇器執行效率的對比。


2.4.1 回顧CSS的選擇器

在解析 jQuery 選擇器引擎 Sizzle 之前,我們不妨回顧一下 CSS 的選擇器 (CSS selector) 。CSS選擇器可以分為三種基本型別:ID選擇器 (#id)、Class選擇器(.class)和型別(type)選擇器(p)。

另外,CSS還支援高階選擇器,如屬性選擇器 (attribute)、偽類或偽物件選擇器 (Pseudo Classes) 等。這些都是單一的選擇器,可以在應用中把它們組合起來,形成組合選擇器,如 div#id,div:last-child。組合型選擇器又包括多種關係形式,如包含關係、並列關係、相鄰關係和父子關係等。

2.4.2 解析 jQuery 選擇器引擎的設計思路

儘管 jQuery 選擇器引擎 Sizzle 非常複雜,功能也非常強大,但是它們都是建立在 JavaScript 已定義的方法或屬性基礎上來實現的。這主要包括元素的 getElementsByTagName() 和 getElementById() 方法,以及元素的 childNodes、firstChild、lastChild、nextSibling、parentNode 和 previousSibling 屬性。藉助這些方法和屬性可以直接或間接地選擇相匹配的 DOM 元素。

為了方便講解,我們結合一個選擇器進行說明。選擇器程式碼如下所示。

$("div.red");

這是一個複合選擇器,一搬讀者可以這樣分析:在DOM文件樹中找到 class 屬性等於 "red" 的 div 元素。即一步到位,直接從DOM文件樹中選擇所需要的元素。實際上,如果根據選擇器引擎的工作方式,可以把這個字串拆分成兩步走。

  • 第一步,根據 document.getElementsByTagName() 方法選擇文件樹中的 div 元素集合。
  • 第二步,根據其 class 是否等於 "red" 進行判斷,把不等於該值的元素從結果集中去掉。
不僅是這個選擇器,對於所有形式的複合選擇器,都可以根據這種思路來拆分選擇器,然後分別完成每一部分的操作。
不過,對於 jQuery 選擇器引擎來說,不同版本在設計思路上也存在一些細微的區別。例如,針對下面的選擇器:
$("div p");
早期的 jQuery 選擇器是根據下面的步驟進行解析的。
  • 第一步,根據 document.getElementsByTagName() 方法選擇文件樹中的 div 元素集合。
  • 第二步,迭代 div 元素集合,在所有的 div 元素中查詢每個 div 元素下的 p 元素。
  • 第三步,合併結果。
而 Sizzle 選擇器適當調整了解析的順序。
  • 第一步,根據 document.getElementsByTagName() 方法選擇文件樹中的 p 元素集合。
  • 第二步,迭代 p 元素集合,在所有的 p 元素中查詢每個 p 元素的父級元素。
  • 第三步,檢測父級元素。如果不是 div 元素,則遍歷上一級元素;如果迭代到文件樹的頂層,則排除該 p 元素;如果是 div 元素,則儲存該 p 元素。
經過比較可以看到,Sizzle 選擇器放棄了合併結果操作,直接在遍歷過程中過濾元素,所以導致查詢速度大幅提升。
初步把握了 jQuery 選擇器的基本工作原理,那麼我們就可以瞭解 jQuery 在匹配元素時是如何工作的。例如,對於 $("div[id=value]");選擇器,我們知道 JavaScript 先匹配 div 元素,然後再判斷 div 元素的 id 屬性是否等於 value ,如果不等於就從結果集中刪除掉。而對於 $("div#value"); 選擇器也是一樣,可以看到 div#value 與 div[id=value] 是完全相同的操作。同樣, div.class 也可以轉換為屬性選擇符進行操作。

2.4.3 選擇器和過濾器

根據上面的分析,我們可以把 CSS選擇器拆分為兩大組成部分,或者說可以把選擇器分為以下兩類。
  • 第一類,選擇器 (selector) 。即根據給定的選擇符,從 DOM 文件樹找到相關的元素節點,並儲存到結果集中。
  • 第二類,過濾器 (filter) 。根據表示式的條件,在結果集中過濾元素。
於是,這裡就有一個技術難題:哪些選擇符適合選擇器(selector),哪些選擇符適合過濾器(filter) ?
由於可以直接使用 document.getElementsByTagName() 方法進行選擇,因此對於型別選擇器來說,直接使用選擇即可。
型別選擇器相互之間還可以組成各種形式的複合選擇器。例如:
*
E F
E~F
E+F
E>F
E/F
E
雖然這些特殊形式的型別選擇器由多個選擇器構成,但是它們都可以從文件中直接選擇,故我們可以統一把它們歸為選擇器型別。而對於 ID 選擇器和 Class 選擇器來說:
  • 如果它們在 selector 字串的起始位置,那麼它們也可以完成選擇功能。例如 $("#id");、$(".class");。
  • 如果它們不在起始位置,那麼就應該作為篩選器實現。例如 $("div#id"); 、$("div.class"); 。
實際上,由於 ID 選擇器可以直接呼叫 JavaScript 的 document.getElementById() 方法進行選擇。但是對於 Class 選擇器來說,由於它無法直接進行選擇,因此,我們可以把ID選擇器視為選擇器,而把 Class 選擇器視為過濾器。
對於 Class 選擇器來說,還可以把它轉換為 "*.class" 形式。其中的 "*" 表示先選取範圍內所有的元素,然後再進行過濾,這樣就可以實現統一的程式設計介面。
對於屬性選擇器、偽類選擇器來說,它們只能夠附在其他選擇器後面使用,如果單獨使用,則可以通過在前面新增 * 標籤,以便設計成統一的語法格式,以方便選擇器引擎的工作。它們與 Class 選擇器的工作方式是相同的,即都視為過濾器。
例如,對於下面這個複雜的選擇器來說,包含了型別選擇器、Class選擇器、ID選擇器、屬性選擇器和偽類選擇器。
$("div.red:nth-child(odd)[title=bar]#wrap p");
jQuery 解析的步驟如下。
第一步,選擇 DOM 文件樹中所有的 p 元素,建立初步結果集。
第二步,在結果集中,選擇父級元素為 div 的元素,形成新的結果集。
第三步,在新的結果集中篩選class屬性為 red 的元素
第四步,解析偽類選擇器 :nth-child(odd),在結果集中篩選元素的子元素為偶數的元素。
第五步,解析屬性選擇器 [title=bar] ,在結果集中篩選元素的 title 屬性為 bar 的元素。
第六步,在結果集中篩選 id 等於 wrap 的元素。

2.4.4 Sizzle 引擎結構

jQuery 的CSS 選擇器引擎 Sizzle 共有 1000 多行程式碼,佔據了 jQuery 框架四分之一的份額。這些程式碼被直接呼叫的匿名函式封裝在一個獨立的空間中,外界是無法訪問的。通過下面程式碼可以把引擎介面傳遞給 jQuery 空間下的四個公共函式。
jQuery.find = Sizzle;
jQuery.filter = Sizzle.filter;
jQuery.expr = Sizzle.selectors;
jQuery.expr[":"] = jQuery.expr.filters;
Sizzle 引擎在jQuery 框架中的位置猶如咽喉,起到了核心作用,如圖 2.4 所示。在下面的 jQuery 選擇器邏輯流程圖中,首先,對傳入的選擇符引數進行過濾,只有是表示式字串時,才會進入 jQuery.fn.find() 入口,然後進入 Sizzle 介面 (jQuery.find()) ,在 Sizzle 構造器中分別呼叫 Sizzle.find() 和 Sizzle.filter() 函式完成選擇和過濾操作。

在選擇 (Sizzle.find()) 和過濾 (Sizzle.filter()) 操作過程中,都會經過這樣的處理流程:匹配簡單的選擇器型別 (To match singleselector type) 。
ID 選擇器
Name
Class 選擇器
型別選擇器
........
對於選擇器 (Sizzle.find()) 來說,它會呼叫 Expr.find[type] ,而對於過濾器 (Sizzle.filter()) 來說,它會呼叫 Expr.filter[type],最後分別返回 Sizzle.find() 和 Sizzle.filter()。
在Sizzle.filter() 過程中,它還需要檢測關係選擇符是否存在 (To check any relative exisits?) 。如果存在,則呼叫 Expr.relative[],最後返回結果集。
下面對 Sizzle 引擎的主體結構進行簡單的分析。
  1. <scripttype="text/javascript">
  2. /*!
  3. *SizzleCSSSelectorEngine-v0.9.3
  4. *Copyright2009,TheDojoFoundation
  5. *ReleasedundertheMIT,BSD,andGPLLicenses.
  6. *Moreinformation:http://sizzlejs.com/
  7. */
  8. //把Sizzle引擎封裝在一個獨立的空間中
  9. (function(){
  10. //定義用於塊識別器的正規表示式
  11. varchunker=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^>+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,
  12. done=0,
  13. toString=Object.prototype.toString;
  14. //Sizzle選擇器引擎構造器函式
  15. //引數說明:
  16. //selector:選擇器字串
  17. //context:上下文
  18. //results:結果集
  19. //seed:種子
  20. varSizzle=function(selector,context,results,seed){
  21. //省略的函式體
  22. };
  23. //Sizzle匹配函式
  24. //引數說明:
  25. //expr:匹配表示式
  26. //set:條件設定選項
  27. Sizzle.matches=function(expr,set){
  28. returnSizzle(expr,null,null,set);
  29. };
  30. //Sizzle查詢函式
  31. //引數說明:
  32. //expr:查詢表示式
  33. //context:上下文
  34. //isXML:檢測函式
  35. Sizzle.find=function(expr,context,isXML){
  36. //省略的函式體
  37. };
  38. //Sizzle過濾函式
  39. //引數說明:
  40. //expr:過濾表示式
  41. //set:條件設定選項
  42. //inplace:包含項
  43. //not:排除項
  44. Sizzle.filter=function(expr,set,inplace,not){
  45. //省略的函式體
  46. };
  47. //Sizzle表示式物件
  48. //列舉所用的各種匹配表示式
  49. varExpr=Sizzle.selectors={
  50. //省略成員屬性
  51. };
  52. //省略其他輔助性工具函式和邏輯程式碼暴露的介面
  53. jQuery.find=Sizzle;
  54. jQuery.filter=Sizzle.filter;
  55. jQuery.expr=Sizzle.selectors;
  56. jQuery.expr[":"]=jQuery.expr.filters;
  57. //定義的公共函式
  58. Sizzle.selectors.filters.hidden=function(elem){};
  59. Sizzle.selectors.filters.visible=function(elem){};
  60. Sizzle.selectors.filters.animated=function(elem){};
  61. jQuery.multiFilter=function(expr,elems,not){};
  62. jQuery.dir=function(elem,dir){};
  63. jQuery.nth=function(cur,result,dir,elem){};
  64. jQuery.sibling=function(n,elem){};
  65. return;
  66. window.Sizzle=Sizzle;
  67. })();
  68. </script>
通過上面的結構分析,可以看到 Sizzle 引擎主要包括一個構造器 Sizzle(),三個核心功能函式 matches()、find() 和 filter() ,以及一個表示式物件 selectors 。下面分別對它們進行講解。

相關文章