Selector
模組是對 Zepto
選擇器的擴充套件,使得 Zepto
選擇器也可以支援部分 CSS3
選擇器和 eq
等 Zepto
定義的選擇器。
在閱讀本篇文章之前,最好先閱讀《讀Zepto原始碼之神奇的$》。
讀 Zepto 原始碼系列文章已經放到了github上,歡迎star: reading-zepto
原始碼版本
本文閱讀的原始碼為 zepto1.2.0
GitBook
輔助方法
visible
function visible(elem){
elem = $(elem)
return !!(elem.width() || elem.height()) && elem.css("display") !== "none"
}
判斷元素是否可見。
可見的標準是元素有寬或者高,並且 display
值不為 none
。
filters
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 }
}
定義了一系列的過濾函式,返回符合條件的元素。這些過濾函式會將集合中符合條件的元素過濾出來,是實現相關選擇器的核心。
- visible: 過濾可見元素,匹配
el:visible
選擇器 - hidden: 過濾不可見元素, 匹配
el:hidden
選擇器 - selected: 過濾選中的元素,匹配
el:selected
選擇器 - checked: 過濾勾選中的元素,匹配
el:checked
選擇器 - parent: 返回至少包含一個子元素的元素,匹配
el:parent
選擇器 - first: 返回第一個元素,匹配
el:first
選擇器 - last: 返回最後一個元素,匹配
el:last
選擇器 - eq: 返回指定索引的元素,匹配
el:eq(index)
選擇器 - contains: 返回包含指定文字的元素,匹配
el:contains(text)
- has: 返回匹配指定選擇器的元素,匹配
el:has(sel)
process
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) }))
如果沒有過濾器,則將所有元素返回,如果存在過濾器,則遍歷集合,呼叫對應的過濾器獲取元素,並將新集合的元素去重。
matches
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
匹配的結果,否則使用過濾器過濾出來的結果。
系列文章
- 讀Zepto原始碼之程式碼結構
- 讀Zepto原始碼之內部方法
- 讀Zepto原始碼之工具函式
- 讀Zepto原始碼之神奇的$
- 讀Zepto原始碼之集合操作
- 讀Zepto原始碼之集合元素查詢
- 讀Zepto原始碼之操作DOM
- 讀Zepto原始碼之樣式操作
- 讀Zepto原始碼之屬性操作
- 讀Zepto原始碼之Event模組
- 讀Zepto原始碼之IE模組
- 讀Zepto原始碼之Callbacks模組
- 讀Zepto原始碼之Deferred模組
- 讀Zepto原始碼之Ajax模組
- 讀Zepto原始碼之assets模組
參考
License
署名-非商業性使用-禁止演繹 4.0 國際 (CC BY-NC-ND 4.0)
最後,所有文章都會同步傳送到微信公眾號上,歡迎關注,歡迎提意見:
作者:對角另一面