Selector
模塊是對 Zepto
選擇器的擴展,使得 Zepto
選擇器也能夠支持部分 CSS3
選擇器和 eq
等 Zepto
定義的選擇器。javascript
在閱讀本篇文章以前,最好先閱讀《讀Zepto源碼之神奇的$》。css
讀 Zepto 源碼系列文章已經放到了github上,歡迎star: reading-zeptohtml
本文閱讀的源碼爲 zepto1.2.0java
《reading-zepto》node
function visible(elem){
elem = $(elem)
return !!(elem.width() || elem.height()) && elem.css("display") !== "none"
}複製代碼
判斷元素是否可見。git
可見的標準是元素有寬或者高,而且 display
值不爲 none
。github
var filters = $.expr[':'] = {
visible: function(){ if (visible(this)) return this },
hidden: function(){ if (!visible(this)) return this },
selected: function(){ if (this.selected) return this },
checked: function(){ if (this.checked) return this },
parent: function(){ return this.parentNode },
first: function(idx){ if (idx === 0) return this },
last: function(idx, nodes){ if (idx === nodes.length - 1) return this },
eq: function(idx, _, value){ if (idx === value) return this },
contains: function(idx, _, text){ if ($(this).text().indexOf(text) > -1) return this },
has: function(idx, _, sel){ if (zepto.qsa(this, sel).length) return this }
}複製代碼
定義了一系列的過濾函數,返回符合條件的元素。這些過濾函數會將集合中符合條件的元素過濾出來,是實現相關選擇器的核心。web
el:visible
選擇器el:hidden
選擇器el:selected
選擇器el:checked
選擇器el:parent
選擇器el:first
選擇器el:last
選擇器el:eq(index)
選擇器el:contains(text)
el:has(sel)
var filterRe = new RegExp('(.*):(\\w+)(?:\\(([^)]+)\\))?$\\s*'),
function process(sel, fn) {
sel = sel.replace(/=#\]/g, '="#"]')
var filter, arg, match = filterRe.exec(sel)
if (match && match[2] in filters) {
filter = filters[match[2]], arg = match[3]
sel = match[1]
if (arg) {
var num = Number(arg)
if (isNaN(num)) arg = arg.replace(/^["']|["']$/g, '')
else arg = num
}
}
return fn(sel, filter, arg)
}複製代碼
process
方法是根據參數 sel
,分解出選擇器、僞類名和僞類參數(如 eq
、 has
的參數),根據僞類來選擇對應的 filter
,傳遞給回調函數 fn
。微信
分解參數最主要靠的是 filterRe
這條正則,正則太過複雜,很難解釋,不過用正則可視化網站 regexper.com,能夠很清晰地看到,正則分紅三大組,第一組匹配的是 :
前面的選擇器,第二組匹配的是僞類名,第三組匹配的是僞類參數。函數
sel = sel.replace(/=#\]/g, '="#"]')複製代碼
這段是處理 a[href^=#]
的狀況,其實就是將 #
包在 ""
裏面,以符合標準的屬性選擇器,這是 Zepto
的容錯能力。 這個選擇器不會匹配到 filters
上的過濾函數,最後調用的是 querySelectorAll
方法,具體見《讀Zepto源碼之神奇的$》對 qsa
函數的分析。
if (match && match[2] in filters) {
filter = filters[match[2]], arg = match[3]
sel = match[1]
...
}複製代碼
match[2]
也即第二組匹配的是僞類名,也是對應 filters
中的 key
值,僞類名存在於 filters
中時,則將選擇器,僞類名和僞類參數存入對應的變量。
if (arg) {
var num = Number(arg)
if (isNaN(num)) arg = arg.replace(/^["']|["']$/g, '')
else arg = num
}複製代碼
若是僞類的參數不能夠用 Number
轉換,則參數爲字符串,用正則將字符串先後的 "
或 '
去掉,再賦值給 arg
.
return fn(sel, filter, arg)複製代碼
最後執行回調,將解釋出來的參數傳入回調函數中,將執行結果返回。
###qsa
var zepto = $.zepto, oldQsa = zepto.qsa, oldMatches = zepto.matches,
childRe = /^\s*>/,
classTag = 'Zepto' + (+new Date())
zepto.qsa = function(node, selector) {
return process(selector, function(sel, filter, arg){
try {
var taggedParent
if (!sel && filter) sel = '*'
else if (childRe.test(sel))
taggedParent = $(node).addClass(classTag), sel = '.'+classTag+' '+sel
var nodes = oldQsa(node, sel)
} catch(e) {
console.error('error performing selector: %o', selector)
throw e
} finally {
if (taggedParent) taggedParent.removeClass(classTag)
}
return !filter ? nodes :
zepto.uniq($.map(nodes, function(n, i){ return filter.call(n, i, nodes, arg) }))
})
}複製代碼
改過的 qsa
調用的是 process
方法,在回調函數中處理大部分邏輯。
思路是經過選擇器獲取到全部節點,而後再調用對應僞類的對應方法來過濾出符合條件的節點。
var taggedParent
if (!sel && filter) sel = '*'
else if (childRe.test(sel))
taggedParent = $(node).addClass(classTag), sel = '.'+classTag+' '+sel
var nodes = oldQsa(node, sel)複製代碼
若是選擇器和過濾器都不存在,則將 sel
設置 *
,即獲取全部元素。
若是 >sel
形式的選擇器,查找全部子元素。
這裏的作法是,向元素 node
中添加惟一的樣式名 classTag
,而後用惟同樣式名和選擇器拼接成子元素選擇器。
最後調用原有的 qsa
函數 oldQsa
來獲取符合選擇器的全部元素。
if (taggedParent) taggedParent.removeClass(classTag)複製代碼
若是存在 taggedParent
,則將元素上的 classTag
清理掉。
return !filter ? nodes :
zepto.uniq($.map(nodes, function(n, i){ return filter.call(n, i, nodes, arg) }))複製代碼
若是沒有過濾器,則將全部元素返回,若是存在過濾器,則遍歷集合,調用對應的過濾器獲取元素,並將新集合的元素去重。
zepto.matches = function(node, selector){
return process(selector, function(sel, filter, arg){
return (!sel || oldMatches(node, sel)) &&
(!filter || filter.call(node, null, arg) === node)
})
}複製代碼
matches
也是調用 process
方法,這裏很巧妙地用了 ||
和 &&
的短路操做。
其實要作的事情就是,若是能夠用 oldMatches
匹配,則使用 oldMatches
匹配的結果,不然使用過濾器過濾出來的結果。
署名-非商業性使用-禁止演繹 4.0 國際 (CC BY-NC-ND 4.0)
最後,全部文章都會同步發送到微信公衆號上,歡迎關注,歡迎提意見:
做者:對角另外一面