作者:Ruby's Louvre
原文:http://www.cnblogs.com/rubylouvre/archive/2009/11/22/1608053.html
本來準備開講jQuery源碼學習筆記六的,但心中有佛才能看到佛,有些人連選擇器都不知什么東西,直接講下去,估計有人會看得云里霧里,滿頭霧水了。加之,John Resig有一種把代碼寫得不知所云的魔力,他擁有強大的馭駕代碼的實力,他自己看當然沒問題,其他人則要命了。常常是一句代碼調用幾個方法,每個方法相隔幾十行甚至上百行,而且這些方法還常常是幌子,真正做事是其他代碼。這樣盤根錯節的代碼與Base2有得一拼。最后,選擇器本身也是非常復雜的東西,有必須獨立提出來說一下。
選擇器其實早已實現,不過都是一些不起眼但超常用的方法:getElementById,getElementsByName,getElementsByTagName。但外國人不滿意這些API,于是搞出了getElementsByClassName,進而是getElementsBySelector,支持當時已公布的所有CSS2.1選擇符!然后Prototype用一個$與一個$$把它們統括起來。之所以這樣,其動機顯然易見,如果返回一個就不用索引號去取了!像jQuery那樣無論是返回多少個,反正都包裹在jQuery對象,都要用索引號或get方法把真正DOM元素取出來,因此用不用$$都差不多,于是$統一天下!
但這個包打天下的選擇器其實包括多少東西呢?細算一下,ID選擇器,標簽選擇器,聯合選擇器,屬性選擇器,關系選擇器與偽類選擇器。ID選擇器,就是以#開頭的,標簽選擇器就是tagName,聯合選擇器就是那個逗號,屬性選擇器就是用中括號括起來的字段,里面也非常復雜,可以單純只有屬性名,不用屬性值,如果有屬性值,又分好幾種情況匹配(詳見我的另一篇博文《getElementsByAttribute》)。像getElementsByName應該歸并于屬性選擇器,因為name也是一個屬性。關系選擇符又細分為四個:兄長(~),親子(>),相鄰(+)與后代(空格)。偽類選擇器的成員非常龐大,因為CSS3新添加的這些偽類選擇符也是如此。除了偽元素選擇符與頁面偽類與鏈接偽類等少量無法轉換為選擇器外,其他基本都能。這些符合資格的偽類有目標偽類,結構偽類,語言偽類與UI狀態偽類。語言偽類與UI狀態偽類與屬性選擇符很相近,因此實現手段也一致。目標偽類我們可以通過截取地址欄上的參數,用IE選擇器搞定。結構偽類則又是個大家族,分為兩派:root與其他。:root就是根節點documentElement。其他都可以統稱這子元素過濾器,我們可以根據順序(nth-child)來篩選,可以根據類型來選(only-of-type),又可以組合類型與順序來選,還可以根據元素里面有沒有內容來選(empty),里面還有一些變種,如那些帶first與last的。在jQuery,為了照顧那些美工MM,John Resig又定義許多快捷的選擇符,這個,自己看文檔吧。
兄長選擇符
提示:可修改后代碼再運行!
相鄰選擇符
提示:可修改后代碼再運行!
親子選擇符
提示:可修改后代碼再運行!
好了,我們說一下它的實現原理吧。通常都是選擇一個字符串,最后返回一組元素。如果字符串是“#aaa”就好辦,把前面的"#" 砍掉,用getElementsById去查找便是!如果是"p span"意思是取得所有p元素中的span元素,我們先用document.getElementsByTagName("p")得到所有p元素,然后遍歷里面的p,用currentP.getElementsByTagName("span")就行了。但這都是最理想的情況下,我們要怎樣才知道調用這些API呢?#提醒我們用IE選擇器,但如果#號是包含在引號中呢,如p["gh#erewf"],這里也有#號。因此我們必須處理一下字符串,如把兩邊的空白去掉,把里面不必要的空白去掉,里面的每個空白只占一個字符空間就是,讓我們知道那些后代選擇符就是,多余的沒有必要。我們看jQuery是如何處理的:
提示:可修改后代碼再運行!
一個很強大的正則,不過這樣后代選擇符就看不到了,jQuery在后面一定做了什么補救措施。事實上,jQuery許多東西都不是一步到位,一個方法調用另一個方法,最后不是怎的就解決了其實一下子轉換為數組意義不大,后面還是要其他正則進行深加工,它們都需要進一步的正則匹配,分析當前的數組元素是ID選擇符還是屬性選擇符。
看來大家喜歡jQuery,那我就舉jQuery吧。其他類庫也是用許多正則來處理字符串,來得到它們想要的信息。jQuery外面看起來很漂亮很清致,里面的實現真是相當復雜與惡心。或許John Resig是個心理陰暗的人,他公開源碼但不想別人看懂,故意寫成這個樣子。我更懷疑源碼中那些注釋是給jQuery另兩個作者看的……嘛,都瞎猜的。我們接著看實現流程,上面這些ID,class,name,attr什么的,基本上是用那兩個選擇器document.getElementById與element.getElementsByTagName加getAttribute搞定。猶其是那個標簽選擇器,每個元素節點都有這方法,我們才可以層層遴選正確的元素。每次我們取得一堆元素,然后逐一檢測其屬性與在父元素的位置,得到結果放到一個數組中,然后下一次再重復這樣的步驟。有時,兩個元素都有相同的子元素,我們就需要在這些元素做一些標記,如果沒有這標記的才放進數組,有就跳過。至于這些怎樣實現,這是jQuery源碼要講的。最后附上一個表:
開頭字符 | 代表選擇符 | 判斷方法 |
---|---|---|
# | ID選擇符 | /#((?:[wu00c0-uFFFF_-]|.)+)/ |
. | 類選擇符 | /.((?:[wu00c0-uFFFF_-]|.)+)/ |
后代選擇符 | /^ss*/ | |
[ | 屬性選擇符 | /[s*((?:[wu00c0-uFFFF_-]|.)+)s*(?:(S?=)s*(['"]*)(.*?)3|)s*]/ |
+ | 相鄰選擇符 | /^[>+~]/ |
~ | 兄長選擇符 | /^[>+~]/ |
> | 親子選擇符 | /^[>+~]/ |
: | 偽類選擇符 | /:((?:[wu00c0-uFFFF_-]|.)+)(?:((['"]*)((?:([^)]+)|[^2()]*)+)2))?/ |
, | 聯合選擇符 | /,/ |
字母 | 標簽選擇符 | /^((?:[wu00c0-uFFFF*_-]|.)+)/ |