這篇依然是跟 dom
相關的方法,側重點是跟集合元素查找相關的方法。javascript
讀Zepto源碼系列文章已經放到了github上,歡迎star: reading-zeptojava
本文閱讀的源碼爲 zepto1.2.0node
以前有一章《讀Zepto源碼以內部方法》是專門解讀 zepto
中沒有提供給外部使用的內部方法的,可是有幾個涉及到 dom
的方法沒有解讀,這裏先將本章用到的方法解讀一下。git
zepto.matches = function(element, selector) { if (!selector || !element || element.nodeType !== 1) return false var matchesSelector = element.matches || element.webkitMatchesSelector || element.mozMatchesSelector || element.oMatchesSelector || element.matchesSelector if (matchesSelector) return matchesSelector.call(element, selector) // fall back to performing a selector: var match, parent = element.parentNode, temp = !parent if (temp)(parent = tempParent).appendChild(element) match = ~zepto.qsa(parent, selector).indexOf(element) temp && tempParent.removeChild(element) return match }
matches
方法用於檢測元素( element
)是否匹配特定的選擇器( selector
)。github
瀏覽器也有原生的 matches
方法,可是要到IE9以後才支持。具體見文檔:Element.matches()web
if (!selector || !element || element.nodeType !== 1) return false
這段是確保 selector
和 element
兩個參數都有傳遞,而且 element
參數的 nodeType
爲 ELEMENT_NODE
,如何條件不符合,返回 false
segmentfault
var matchesSelector = element.matches || element.webkitMatchesSelector || element.mozMatchesSelector || element.oMatchesSelector || element.matchesSelector if (matchesSelector) return matchesSelector.call(element, selector)
這段是檢測瀏覽器是否原生支持 matches
方法,或者支持帶私有前綴的 matches
方法,若是支持,調用原生的 matches
,並將結果返回。數組
var match, parent = element.parentNode, temp = !parent if (temp)(parent = tempParent).appendChild(element) match = ~zepto.qsa(parent, selector).indexOf(element) temp && tempParent.removeChild(element) return match
若是原生的方法不支持,則回退到用選擇器的方法來檢測。瀏覽器
這裏定義了三個變量,其中 parent
用來存放 element
的父節點, temp
用來判斷 element
是否有父元素。值爲 temp = !parent
,若是 element
存在父元素,則 temp
的值爲 false
。微信
首先判斷是否存在父元素,若是父元素不存在,則 parent = tempParent
,tempParent
已經由一個全局變量來定義,爲 tempParent = document.createElement('div')
,其實就是一個 div
空節點。而後將 element
插入到空節點中。
而後,查找 parent
中全部符合選擇器 selector
的元素集合,再找出當前元素 element
在集合中的索引。
zepto.qsa(parent, selector).indexOf(element)
再對索引進行取反操做,這樣索引值爲 0
的值就變成了 -1
,是 -1
的返回的是 0
,這樣就確保了跟 matches
的表現一致。
其實我有點不太懂的是,爲何不跟原生同樣,返回 boolean
類型的值呢?明明經過 zepto.qsa(parent, selector).indexOf(element) > -1
就能夠作到了,接口表現一致不是更好嗎?
最後還有一步清理操做:
temp && tempParent.removeChild(element)
將空接點的子元素清理點,避免污染。
function children(element) { return 'children' in element ? slice.call(element.children) : $.map(element.childNodes, function(node) { if (node.nodeType == 1) return node }) }
children
方法返回的是 element
的子元素集合。
瀏覽器也有原生支持元素 children
屬性,也要到IE9以上才支持,見文檔ParentNode.children
若是檢測到瀏覽器不支持,則降級用 $.map
方法,獲取 element
的 childNodes
中 nodeType
爲 ELEMENT_NODE
的節點。由於 children
返回的只是元素節點,可是 childNodes
返回的除元素節點外,還包含文本節點、屬性等。
這裏用到的 $.map
跟數組的原生方法 map
表現有區別,關於 $.map
的具體實現,已經在《讀zepto源碼之工具函數》解讀過了。
function filtered(nodes, selector) { return selector == null ? $(nodes) : $(nodes).filter(selector) }
將匹配指定選擇器的元素從集合中過濾出來。
若是沒有指定 selector
,則將集合包裹成 zepto
對象所有返回,不然調用 filter
方法,過濾出符合條件的元素返回。filter
方法下面立刻講到。
這裏的方法都是 $.fn
中提供的方法。
filter: function(selector) { if (isFunction(selector)) return this.not(this.not(selector)) return $(filter.call(this, function(element) { return zepto.matches(element, selector) })) }
filter
是查找符合條件的元素集合。
參數 selector
能夠爲 Function
或者選擇器,當爲 Function
時,調用的其實調用了兩次 not
方法,負負得正。關於 not
方法,下面立刻會看到。
當爲通常的選擇器時,調用的是filter
方法,filter
的回調函數調用了 matches
,將符合 selector
的元素返回,幷包裝成 zepto
對象返回。
not: function(selector) { var nodes = [] if (isFunction(selector) && selector.call !== undefined) this.each(function(idx) { if (!selector.call(this, idx)) nodes.push(this) }) else { var excludes = typeof selector == 'string' ? this.filter(selector) : (likeArray(selector) && isFunction(selector.item)) ? slice.call(selector) : $(selector) this.forEach(function(el) { if (excludes.indexOf(el) < 0) nodes.push(el) }) } return $(nodes) }
not
方法是將集合中不符合條件的元素查找出來。
not
方法的方法有三種調用方式:
not(selector) ⇒ collection not(collection) ⇒ collection not(function(index){ ... }) ⇒ collection
當 selector
爲 Function
,而且有 call
方法時(isFunction(selector) && selector.call !== undefined
),相關的代碼以下:
this.each(function(idx) { if (!selector.call(this, idx)) nodes.push(this) })
調用 each
方法,而且在 selector
函數中,能夠訪問到當前的元素和元素的索引。若是 selector
函數的值取反後爲 true
,則將相應的元素放入 nodes
數組中。
當 selector
不爲 Function
時, 定義了一個變量 excludes
,這個變量來用接收須要排除的元素集合。接下來又是一串三元表達式(zepto的特點啊)
typeof selector == 'string' ? this.filter(selector)
當 selector
爲 string
時,調用 filter
,找出全部須要排除的元素
(likeArray(selector) && isFunction(selector.item)) ? slice.call(selector) : $(selector)
這段我剛開始看時,有點困惑,主要是不明白 isFunction(selector.item)
這個判斷條件,後來查了MDN文檔HTMLCollection.item,才明白 item
是 HTMLCollection
的一個方法,這個三元表達式的意思是,若是是 HTMLCollection
,則調用 slice.call
獲得一個純數組,不然返回 zepto
對象。
this.forEach(function(el) { if (excludes.indexOf(el) < 0) nodes.push(el) })
遍歷集合,若是元素不在須要排除的元素集合中,將該元素 push
進 nodes
中。
not
方法最終返回的也是 zepto
對象。
is: function(selector) { return this.length > 0 && zepto.matches(this[0], selector) }
判斷集合中的第一個元素是否匹配指定的選擇器。
代碼也比較簡單了,選判斷集合不爲空,再調用 matches
看第一個元素是否匹配。
find: function(selector) { var result, $this = this if (!selector) result = $() else if (typeof selector == 'object') result = $(selector).filter(function() { var node = this return emptyArray.some.call($this, function(parent) { return $.contains(parent, node) }) }) else if (this.length == 1) result = $(zepto.qsa(this[0], selector)) else result = this.map(function() { return zepto.qsa(this, selector) }) return result }
find
是查找集合中符合選擇器的全部後代元素,若是給定的是 zepto
對象或者 dom
元素,則只有他們在當前的集合中時,才返回。
fid
有三種調用方式,以下:
find(selector) ⇒ collection find(collection) ⇒ collection find(element) ⇒ collection
if (!selector) result = $()
若是不傳參時,返回的是空的 zepto
對象。
else if (typeof selector == 'object') result = $(selector).filter(function() { var node = this return emptyArray.some.call($this, function(parent) { return $.contains(parent, node) }) })
若是傳參爲 object
時,也就是 zepto
對象collection
和dom
節點 element
時,先將 selector
包裹成 zepto
對象,而後對這個對象過濾,返回當前集合子節點中所包含的元素($.contains(parent, node)
)。
else if (this.length == 1) result = $(zepto.qsa(this[0], selector))
若是當前的集合只有一個元素時,直接調用 zepto.qsa
方法,取出集合的第一個元素 this[0]
做爲 qsa
的第一個參數。關於 qsa
方法,已經在《讀Zepto源碼之神奇的$》分析過了。其實就是獲取第一個元素的全部後代元素。
else result = this.map(function() { return zepto.qsa(this, selector) })
不然,調用 map
方法,對集合中每一個元素都調用 qsa
方法,獲取全部元素的後代元素。這個條件其實能夠與上一個條件合併的,分開應該是爲了性能的考量。
has: function(selector) { return this.filter(function() { return isObject(selector) ? $.contains(this, selector) : $(this).find(selector).size() }) },
判斷集合中是否有包含指定條件的子元素,將符合條件的元素返回。
有兩種調用方式
has(selector) ⇒ collection has(node) ⇒ collection
參數能夠爲選擇器或者節點。
has
其實調用的是 filter
方法,這個方法上面已經解讀過了。filter
的回調函數中根據參數的不一樣狀況,調用了不一樣的方法。
isObject(selector)
用來判斷 selector
是否爲 node
節點,若是爲 node
節點,則調用 $.contains
方法,該方法已經在《讀Zepto源碼之工具函數》說過了。
若是爲選擇器,則調用 find
方法,而後再調用 size
方法,size
方法返回的是集合中元素的個數。這個在《讀Zepto源碼之集合操做》有講過,若是集合個數大於零,則表示知足條件。
eq: function(idx) { return idx === -1 ? this.slice(idx) : this.slice(idx, +idx + 1) },
獲取集合中指定的元素。
這裏調用了 slice
方法,這個方法在上一篇《讀Zepto源碼之集合操做》已經說過了。若是 idx
爲 -1
時,直接調用 this.slice(idx)
,即取出最後一個元素,不然取 idx
至 idx + 1
之間的元素,也就是每次只取一個元素。+idx+1
前面的 +
號實際上是類型轉換,確保 idx
在作加法的時候爲 Number
類型。
first: function() { var el = this[0] return el && !isObject(el) ? el : $(el) },
first
是取集合中第一個元素,這個方法很簡單,用索引 0
就能夠取出來了,也就是 this[0]
。
el && !isObject(el)
用來判斷是否爲 zepto
對象,若是不是,用 $(el)
包裹,確保返回的是 zepto
對象。
last: function() { var el = this[this.length - 1] return el && !isObject(el) ? el : $(el) },
last
是取集合中最後一個元素,這個的原理跟 first
同樣,只不過變成了取索引值爲 this.length - 1
,也就是最後的元素。
closest: function(selector, context) { var nodes = [], collection = typeof selector == 'object' && $(selector) this.each(function(_, node) { while (node && !(collection ? collection.indexOf(node) >= 0 : zepto.matches(node, selector))) node = node !== context && !isDocument(node) && node.parentNode if (node && nodes.indexOf(node) < 0) nodes.push(node) }) return $(nodes) },
從元素自己向上查找,返回最早符合條件的元素。
這個方法也有三種調用方式
closest(selector, [context]) ⇒ collection closest(collection) ⇒ collection closest(element) ⇒ collection
若是指定了 zepto
集合或者 element
,則只返回匹配給定集合或 element
的元素。
collection = typeof selector == 'object' && $(selector)
這段是判斷 selector
是否爲 collection
或 element
,若是是,則統一轉化爲 zepto
集合。
而後對集合遍歷,在 each
遍歷裏針對集合中每一個 node
節點,都用 while
語句,向上查找符合條件的元素。
node && !(collection ? collection.indexOf(node) >= 0 : zepto.matches(node, selector))
這段是 while
語句的終止條件。 node
節點必須存在,若是 selector
爲 zepto
集合或者 element
,也即 collection
存在, 則要找到存在於 collection
中的節點(collection.indexOf(node) >= 0
), 不然,節點要匹配指定的選擇器(zepto.matches(node, selector)
)
在 while
循環中,是向上逐級查找節點的過程:
node = node !== context && !isDocument(node) && node.parentNode
當前 node
不爲指定的上下文 context
而且不爲 document
節點時,向上查找(node.parentNode
)
if (node && nodes.indexOf(node) < 0) nodes.push(node)
while
循環完畢後,若是 node
節點存在,而且 nodes
中還不存在 node
,則將 node
push 進 nodes
中。
最後返回 zepto
集合。
pluck: function(property) { return $.map(this, function(el) { return el[property] }) },
返回集合中全部元素指定的屬性值。
這個方法很簡單,就是對當前集合遍歷,而後取元素指定的 property
值。
parents: function(selector) { var ancestors = [], nodes = this while (nodes.length > 0) nodes = $.map(nodes, function(node) { if ((node = node.parentNode) && !isDocument(node) && ancestors.indexOf(node) < 0) { ancestors.push(node) return node } }) return filtered(ancestors, selector) },
返回集合中全部元素的全部祖先元素。
nodes
的初始值爲當前集合,while
循環的條件爲集合不爲空。
使用 map
遍歷 nodes
,將 node
從新賦值爲自身的父級元素,若是父級元素存在,而且不是 document
元素,並且還不存在於 ancestors
中時,將 node
存入保存祖先元素的 ancestors
中,而且 map
回調的返回值是 node
,組成新的集合賦值給 nodes
,直到全部的祖先元素遍歷完畢,就能夠退出 while
循環。
最後,調用上面說到的 filtered
方法,找到符合 selector
的祖先元素。
parent: function(selector) { return filtered(uniq(this.pluck('parentNode')), selector) },
返回集合中全部元素的父級元素。
parents
返回的是全部祖先元素,而 parent
返回只是父級元素。
首先調用的是 this.pluck('parentNode')
,獲取全部元素的祖先元素,而後調用 uniq
對集合去重,最後調用 filtered
,返回匹配 selector
的元素集合。
children: function(selector) { return filtered(this.map(function() { return children(this) }), selector) },
返回集合中全部元素的子元素。
首先對當前集合遍歷,調用內部方法 children
獲取當前元素的子元素組成新的數組,再調用 filtered
方法返回匹配 selector
的元素集合。
contents: function() { return this.map(function() { return this.contentDocument || slice.call(this.childNodes) }) },
這個方法相似於 children
,不過 children
對 childNodes
進行了過濾,只返回元素節點。contents
還返回文本節點和註釋節點。也返回 iframe
的 contentDocument
siblings: function(selector) { return filtered(this.map(function(i, el) { return filter.call(children(el.parentNode), function(child) { return child !== el }) }), selector) },
獲取全部集合中全部元素的兄弟節點。
獲取兄弟節點的思路也很簡單,對當前集合遍歷,找到當前元素的父元素el.parentNode
,調用 children
方法,找出父元素的子元素,將子元素中與當前元素不相等的元素過濾出來便是其兄弟元素了。
最後調用 filtered
來過濾出匹配 selector
的兄弟元素。
prev: function(selector) { return $(this.pluck('previousElementSibling')).filter(selector || '*') },
獲取集合中每一個元素的前一個兄弟節點。
這個方法也很簡單,調用 pluck
方法,獲取元素的 previousElementSibling
屬性,即爲元素的前一個兄弟節點。再調用 filter
返回匹配 selector
的元素,最後包裹成 zepto
對象返回
next: function(selector) { return $(this.pluck('nextElementSibling')).filter(selector || '*') },
next
方法跟 prev
方法相似,只不過取的是 nextElementSibling
屬性,獲取的是每一個元素的下一個兄弟節點。
index: function(element) { return element ? this.indexOf($(element)[0]) : this.parent().children().indexOf(this[0]) },
返回指定元素在當前集合中的位置(this.indexOf($(element)[0])
),若是沒有給出 element
,則返回當前鮮紅在兄弟元素中的位置。this.parent().children()
查找的是兄弟元素。
最後,全部文章都會同步發送到微信公衆號上,歡迎關注,歡迎提意見:
做者:對角另外一面