jQuery技術解密六

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 引擎的主體結構進行簡單的分析。
[html] view plaincopy


  1. <script type="text/javascript">  
  2. /*!  
  3.  * Sizzle CSS Selector Engine - v0.9.3  
  4.  *  Copyright 2009, The Dojo Foundation  
  5.  *  Released under the MIT, BSD, and GPL Licenses.  
  6.  *  More information: http://sizzlejs.com/  
  7.  */  
  8.    
  9. // 把 Sizzle 引擎封裝在一個獨立的空間中      
  10. (function(){  
  11. // 定義用於塊識別器的正則表達式  
  12. var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|
    (?:\[[[
    ]*\]|[‘"][^’"]*[‘"]|[^[\]’"]+)+\]|\\.|[^ 
    >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,  
  13.     done = 0,  
  14.     toString = Object.prototype.toString;  
  15. // Sizzle 選擇器引擎構造器函數  
  16. // 參數說明:  
  17. // selector: 選擇器字符串  
  18. // context: 上下文  
  19. // results: 結果集  
  20. // seed: 種子  
  21. var Sizzle = function(selector, context, results, seed) {  
  22.     // 省略的函數體  
  23. };  
  24. // Sizzle 匹配函數  
  25. // 參數說明:  
  26. // expr: 匹配表達式  
  27. // set: 條件設置選項  
  28. Sizzle.matches = function(expr, set){  
  29.     return Sizzle(expr, null, null, set);  
  30. };  
  31. // Sizzle 查詢函數  
  32. // 參數說明:  
  33. // expr: 查詢表達式  
  34. // context: 上下文  
  35. // isXML: 檢測函數  
  36. Sizzle.find = function(expr, context, isXML){  
  37.     // 省略的函數體  
  38. };    
  39. // Sizzle 過濾函數  
  40. // 參數說明:  
  41. // expr: 過濾表達式  
  42. // set: 條件設置選項  
  43. // inplace: 包含項  
  44. // not: 排除項  
  45. Sizzle.filter = function(expr, set, inplace, not){  
  46.     // 省略的函數體  
  47. };  
  48. // Sizzle 表達式對象  
  49. // 列舉所用的各種匹配表達式  
  50. var Expr = Sizzle.selectors = {  
  51.     // 省略成員屬性  
  52. };  
  53. // 省略其他輔助性工具函數和邏輯代碼暴露的接口  
  54. jQuery.find = Sizzle;  
  55. jQuery.filter = Sizzle.filter;  
  56. jQuery.expr = Sizzle.selectors;  
  57. jQuery.expr[":"] = jQuery.expr.filters;  
  58. // 定義的公共函數  
  59. Sizzle.selectors.filters.hidden = function(elem){};  
  60. Sizzle.selectors.filters.visible = function(elem){};  
  61. Sizzle.selectors.filters.animated = function(elem){};  
  62. jQuery.multiFilter = function(expr, elems, not){};  
  63. jQuery.dir = function(elem, dir){};  
  64. jQuery.nth = function(cur, result, dir, elem){};  
  65. jQuery.sibling = function(n, elem){};  
  66. return;  
  67. window.Sizzle = Sizzle;  
  68. })();   
  69. </script>  


通過上面的結構分析,可以看到 Sizzle 引擎主要包括一個構造器 Sizzle(),三個核心功能函數 matches()、find() 和 filter() ,以及一個表達式對象 selectors 。下面分別對它們進行講解。
發佈了19 篇原創文章 · 獲贊 8 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章