讀Zepto源碼之Selector模塊

Selector 模塊是對 Zepto 選擇器的擴(kuò)展,使得 Zepto 選擇器也可以支持部分 CSS3 選擇器和 eqZepto 定義的選擇器。

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

讀 Zepto 源碼系列文章已經(jīng)放到了github上,歡迎star: reading-zepto

源碼版本

本文閱讀的源碼為 zepto1.2.0

GitBook

reading-zepto

輔助方法

visible

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

判斷元素是否可見。

可見的標(biāo)準(zhǔn)是元素有寬或者高,并且 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 }
}

定義了一系列的過濾函數(shù),返回符合條件的元素。這些過濾函數(shù)會將集合中符合條件的元素過濾出來,是實(shí)現(xiàn)相關(guān)選擇器的核心。

  • visible: 過濾可見元素,匹配 el:visible 選擇器
  • hidden: 過濾不可見元素, 匹配 el:hidden 選擇器
  • selected: 過濾選中的元素,匹配 el:selected 選擇器
  • checked: 過濾勾選中的元素,匹配 el:checked 選擇器
  • parent: 返回至少包含一個(gè)子元素的元素,匹配 el:parent 選擇器
  • first: 返回第一個(gè)元素,匹配 el:first 選擇器
  • last: 返回最后一個(gè)元素,匹配 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 方法是根據(jù)參數(shù) sel,分解出選擇器、偽類名和偽類參數(shù)(如 eq 、 has 的參數(shù)),根據(jù)偽類來選擇對應(yīng)的 filter ,傳遞給回調(diào)函數(shù) fn 。

分解參數(shù)最主要靠的是 filterRe 這條正則,正則太過復(fù)雜,很難解釋,不過用正則可視化網(wǎng)站 regexper.com,可以很清晰地看到,正則分成三大組,第一組匹配的是 : 前面的選擇器,第二組匹配的是偽類名,第三組匹配的是偽類參數(shù)。

sel = sel.replace(/=#\]/g, '="#"]')

這段是處理 a[href^=#] 的情況,其實(shí)就是將 # 包在 "" 里面,以符合標(biāo)準(zhǔn)的屬性選擇器,這是 Zepto 的容錯能力。 這個(gè)選擇器不會匹配到 filters 上的過濾函數(shù),最后調(diào)用的是 querySelectorAll 方法,具體見《讀Zepto源碼之神奇的$》對 qsa 函數(shù)的分析。

if (match && match[2] in filters) {
  filter = filters[match[2]], arg = match[3]
  sel = match[1]
  ...
}

match[2] 也即第二組匹配的是偽類名,也是對應(yīng) filters 中的 key 值,偽類名存在于 filters 中時(shí),則將選擇器,偽類名和偽類參數(shù)存入對應(yīng)的變量。

if (arg) {
  var num = Number(arg)
  if (isNaN(num)) arg = arg.replace(/^["']|["']$/g, '')
  else arg = num
}

如果偽類的參數(shù)不可以用 Number 轉(zhuǎn)換,則參數(shù)為字符串,用正則將字符串前后的 "' 去掉,再賦值給 arg.

return fn(sel, filter, arg)

最后執(zhí)行回調(diào),將解釋出來的參數(shù)傳入回調(diào)函數(shù)中,將執(zhí)行結(jié)果返回。

重寫的方法

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 調(diào)用的是 process 方法,在回調(diào)函數(shù)中處理大部分邏輯。

思路是通過選擇器獲取到所有節(jié)點(diǎn),然后再調(diào)用對應(yīng)偽類的對應(yīng)方法來過濾出符合條件的節(jié)點(diǎn)。

處理選擇器,根據(jù)選擇器獲取節(jié)點(diǎn)

var taggedParent
if (!sel && filter) sel = '*'
else if (childRe.test(sel))
  taggedParent = $(node).addClass(classTag), sel = '.'+classTag+' '+sel

var nodes = oldQsa(node, sel)

如果選擇器和過濾器都不存在,則將 sel 設(shè)置 * ,即獲取所有元素。

如果 >sel 形式的選擇器,查找所有子元素。

這里的做法是,向元素 node 中添加唯一的樣式名 classTag,然后用唯一樣式名和選擇器拼接成子元素選擇器。

最后調(diào)用原有的 qsa 函數(shù) oldQsa 來獲取符合選擇器的所有元素。

清理所添加的樣式

if (taggedParent) taggedParent.removeClass(classTag)

如果存在 taggedParent ,則將元素上的 classTag 清理掉。

調(diào)用對應(yīng)的過濾器,過濾元素

return !filter ? nodes :
    zepto.uniq($.map(nodes, function(n, i){ return filter.call(n, i, nodes, arg) }))

如果沒有過濾器,則將所有元素返回,如果存在過濾器,則遍歷集合,調(diào)用對應(yīng)的過濾器獲取元素,并將新集合的元素去重。

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 也是調(diào)用 process 方法,這里很巧妙地用了 ||&& 的短路操作。

其實(shí)要做的事情就是,如果可以用 oldMatches 匹配,則使用 oldMatches 匹配的結(jié)果,否則使用過濾器過濾出來的結(jié)果。

系列文章

  1. 讀Zepto源碼之代碼結(jié)構(gòu)
  2. 讀Zepto源碼之內(nèi)部方法
  3. 讀Zepto源碼之工具函數(shù)
  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源碼之a(chǎn)ssets模塊

參考

License

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

最后,所有文章都會同步發(fā)送到微信公眾號上,歡迎關(guān)注,歡迎提意見:

作者:對角另一面

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 這篇依然是跟 dom 相關(guān)的方法,側(cè)重點(diǎn)是跟集合元素查找相關(guān)的方法。 讀Zepto源碼系列文章已經(jīng)放到了githu...
    對角另一面閱讀 332評論 0 0
  • Ajax 模塊也是經(jīng)常會用到的模塊,Ajax 模塊中包含了 jsonp 的現(xiàn)實(shí),和 XMLHttpRequest ...
    對角另一面閱讀 660評論 0 1
  • Form 模塊處理的是表單提交。表單提交包含兩部分,一部分是格式化表單數(shù)據(jù),另一部分是觸發(fā) submit 事件,提...
    對角另一面閱讀 617評論 0 0
  • 藝藝說要和我談?wù)劦臅r(shí)候,我就猜到了她肯定要和我談軍營的事。藝藝是個(gè)擁軍姑娘,我義務(wù)兵的時(shí)候認(rèn)識她,轉(zhuǎn)眼我都第四年了...
    渠六億閱讀 823評論 3 4
  • 每天日子在有條不紊地進(jìn)行著…學(xué)業(yè),是我覺得能體驗(yàn)到我還是個(gè)學(xué)生的東西,但是,大學(xué)絕不僅僅是學(xué)習(xí),有社團(tuán),有很多其他...
    CYF_BY閱讀 178評論 0 0

友情鏈接更多精彩內(nèi)容