讀Zepto源碼之Selector模塊

Selector 模塊是對 Zepto 選擇器的擴展,使得 Zepto 選擇器也能夠支持部分 CSS3 選擇器和 eqZepto 定義的選擇器。javascript

在閱讀本篇文章以前,最好先閱讀《讀Zepto源碼之神奇的$》。css

讀 Zepto 源碼系列文章已經放到了github上,歡迎star: reading-zeptohtml

源碼版本

本文閱讀的源碼爲 zepto1.2.0java

GitBook

reading-zeptonode

輔助方法

visible

function visible(elem){
  elem = $(elem)
  return !!(elem.width() || elem.height()) && elem.css("display") !== "none"
}複製代碼

判斷元素是否可見。git

可見的標準是元素有寬或者高,而且 display 值不爲 nonegithub

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 }
}複製代碼

定義了一系列的過濾函數,返回符合條件的元素。這些過濾函數會將集合中符合條件的元素過濾出來,是實現相關選擇器的核心。web

  • 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,分解出選擇器、僞類名和僞類參數(如 eqhas 的參數),根據僞類來選擇對應的 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 匹配的結果,不然使用過濾器過濾出來的結果。

系列文章

  1. 讀Zepto源碼之代碼結構
  2. 讀Zepto源碼以內部方法
  3. 讀Zepto源碼之工具函數
  4. 讀Zepto源碼之神奇的$
  5. 讀Zepto源碼之集合操做
  6. 讀Zepto源碼之集合元素查找
  7. 讀Zepto源碼之操做DOM
  8. 讀Zepto源碼之樣式操做
  9. 讀Zepto源碼之屬性操做
  10. 讀Zepto源碼之Event模塊
  11. 讀Zepto源碼之IE模塊
  12. 讀Zepto源碼之Callbacks模塊
  13. 讀Zepto源碼之Deferred模塊
  14. 讀Zepto源碼之Ajax模塊
  15. 讀Zepto源碼之assets模塊

參考

License

署名-非商業性使用-禁止演繹 4.0 國際 (CC BY-NC-ND 4.0)

最後,全部文章都會同步發送到微信公衆號上,歡迎關注,歡迎提意見:

做者:對角另外一面

相關文章
相關標籤/搜索