dojo/query子產品是dojo為開發者提供的dom查詢接口。該子產品的輸出對象是一個使用css選擇符來查詢dom元素并傳回nodelist對象的函數。同時,dojo/query子產品也是一個插件,開發者可以使用自定義的查詢引擎,query子產品會負責将引擎的查詢結果包裝成dojo自己的nodelist對象。
要了解這個子產品就要搞清楚兩個問題:
如何查詢,查詢的原理?
查詢結果是什麼,如何處理查詢結果?
這兩個問題涉及到本文的兩個主題:選擇器引擎和nodelist。
選擇器引擎
前端的工作必然涉及到與dom節點打交道,我們經常需要對一個dom節點進行一系列的操作。但我們如何找到這個dom節點呢,為此我們需要一種語言來告訴浏覽器我們想要就是這個語言描述的dom節點,這種語言就是css選擇器。比如我們想浏覽器描述一個dom節點:div > p + .bodhi input[type="checkbox"],它的意思是在div元素下的直接子元素p的下一個class特性中含有bodhi的兄弟節點下的type屬性是checkbox的input元素。
選擇符種類
元素選擇符:通配符*、類型選擇符e、類選擇符e.class、id選擇符e#id
關系選擇符:包含(e f)、子選擇符(e>f)、相鄰選擇符(e+f)、兄弟選擇符(e~f)
屬性選擇符: e[att]、e[att="val"]、e[att~="val"]、e[att^="val"]、e[att$="val"]、e[att*="val"]
僞類選擇符
僞對象選擇符:e:first-letter、e:first-line、e:before、e:after、e::placehoser、e::selection
通過選擇器來查詢dom節點,最簡單的方式是依靠浏覽器提供的幾個原生接口:getelementbyid、getelementsbytagname、getelementsbyname、getelementsbyclassname、queryselector、queryselectorall。但因為低版本浏覽器不完全支援這些接口,而我們實際工作中有需要這些某些進階接口,是以才會有各種各樣的選擇器引擎。是以選擇器引擎就是幫我們查詢dom節點的代碼類庫。
選擇器引擎很簡單,但是一個高校的選擇器引擎會涉及到詞法分析和預編譯。不懂編譯原理的我表示心有餘而力不足。
但需要知道的一點是:解析css選擇器的時候,都是按照從右到左的順序來的,目的就是為了提高效率。比如“div p span.bodhi”;如果按照正向查詢,我們首先要找到div元素集合,從集合中拿出一個元素,再找其中的p集合,p集合中拿出一個元素找class屬性是bodhi的span元素,如果沒找到重新回到開頭的div元素,繼續查找。這樣的效率是極低的。相反,如果按照逆向查詢,我們首先找出class為bodhi的span元素集合,在一直向上回溯看看祖先元素中有沒有選擇符内的元素即可,孩子找父親很容易,但父親找孩子是困難的。
選擇器引擎為了優化效率,每一個選擇器都可以被分割為好多部分,每一部分都會涉及到标簽名(tag)、特性(attr)、css類(class)、僞節點(persudo)等,分割的方法與選擇器引擎有關。比如選擇器 div > p + .bodhi input[type="checkbox"]如果按照空格來分割,那它會被分割成以下幾部分:
div
>
p
+
.bodhi
input[type="checkbox"]
對于每一部分選擇器引擎都會使用一種資料結構來表達這些選擇符,如dojo中acme使用的結構:
從這裡可以看到有專門的結構來管理不同的類型的選擇符。分割出來的每一部分在acme中都會生成一個part,part中有tag、僞元素、屬性、元素關系等。。;所有的part都被放到queryparts數組中。然後從右到左每次便利一個part,低版本浏覽器雖然不支援進階接口,但是一些低級接口還是支援的,比如:getelementsby*;對于一個part,先比對tag,然後判斷class、attr、id等。這是一種解決方案,但這種方案有很嚴重的效率問題。(後面這句是猜想)試想一下:我們可不可以把一個part中有效的幾項的判斷函數來組裝成一個函數,對于一個part隻執行一次即可。沒錯,acme就是這樣來處理的(這裡涉及到預編譯問題,看不明白的自動忽略即可。。。)
dojo/query子產品的選擇器引擎通過dojo/selector/loader來加載。如果沒有在dojoconfig中配置selectorengine屬性,那麼loader子產品會自己判斷使用acme和是lite引擎,原則是高版本浏覽器盡量使用lite,而低版本盡量使用acme。
選擇器引擎的代碼晦澀難懂,我們隻需要關心最終暴露出來的接口的用法即可。
acme:
lite:
nodelist
nodelist來自于原生dom,是一系列dom節點的集合,一個類素組對象,mdn中的解釋:
我們看到document.queryselectorall方法傳回一個nodelist對象,而且這個方法傳回的nodelist對象是一個靜态的集合。
是以大多數的前端類庫都參照原生設計,查詢接口傳回的都是一個靜态集合。隻是有的明确點明如dojo,有的含蓄的實作如jquery。
要了解dojo中的nodelist需要把握以下規則:
dojo中的nodelist就是擴充了能力的array執行個體。是以需要一個函數将原生array包裝起來
nodelist的任何方法傳回的還是nodelist執行個體。就像array的slice、splice還是傳回一個array一樣
has("array-extensible")的作用是判斷數組是否可以被繼承,如果原生的數組是可被繼承的,那就将nodelist的原型指向一個數組執行個體,否則指向普通對象。
下面這句話需要紮實的基本功,如果了解這句話,整個脈絡就會變得清晰起來。
new的作用等于如下函數:
放到nodelist身上是這樣的:
isnew為true,保證了nodelist執行個體是一個經過擴充的array對象。
nodelist函數的源碼:
可以看到如果isnew為false,那就對一個新的array對象進行擴充。
擴充的能力,便是直接在nodelist.prototype上增加的方法。大家直接看源碼和我的注釋即可。
nodelist提供的很多操作,如:map、filter、concat等,都是借助原生的array提供的相應方法,這些方法傳回都是原生的array對象,是以需要對傳回的array對象進行包裝。有趣的是nodelist提供end()可以回到原始的nodelist中。整個結構如下:

我們來看一下包裝函數:
這就是dojo中nodelist的設計!
query子產品暴露的方法無非就是對選擇器引擎的調用,下面就比較簡單了。
如果您覺得這篇文章對您有幫助,請不吝點選推薦,您的鼓勵是我分享的動力!!!