讀 Zepto 源碼之集合元素查找

来源:http://www.cnblogs.com/hefty/archive/2017/05/30/6920524.html
-Advertisement-
Play Games

這篇依然是跟 相關的方法,側重點是跟集合元素查找相關的方法。 讀Zepto源碼系列文章已經放到了github上,歡迎star: "reading zepto" 源碼版本 本文閱讀的源碼為 "zepto1.2.0" 內部方法 之前有一章《 "讀Zepto源碼之內部方法" 》是專門解讀 中沒有提供給外部 ...


這篇依然是跟 dom 相關的方法,側重點是跟集合元素查找相關的方法。

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

源碼版本

本文閱讀的源碼為 zepto1.2.0

內部方法

之前有一章《讀Zepto源碼之內部方法》是專門解讀 zepto 中沒有提供給外部使用的內部方法的,但是有幾個涉及到 dom 的方法沒有解讀,這裡先將本章用到的方法解讀一下。

matches

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 )。

瀏覽器也有原生的 matches 方法,但是要到IE9之後才支持。具體見文檔:Element.matches()

if (!selector || !element || element.nodeType !== 1) return false

這段是確保 selectorelement 兩個參數都有傳遞,並且 element 參數的 nodeTypeELEMENT_NODE ,如何條件不符合,返回 false

 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 = tempParenttempParent 已經由一個全局變數來定義,為 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)

將空接點的子元素清理點,避免污染。

children

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 方法,獲取 elementchildNodesnodeTypeELEMENT_NODE 的節點。因為 children 返回的只是元素節點,但是 childNodes 返回的除元素節點外,還包含文本節點、屬性等。

這裡用到的 $.map 跟數組的原生方法 map 表現有區別,關於 $.map 的具體實現,已經在《讀zepto源碼之工具函數》解讀過了。

filtered

function filtered(nodes, selector) {
  return selector == null ? $(nodes) : $(nodes).filter(selector)
}

將匹配指定選擇器的元素從集合中過濾出來。

如果沒有指定 selector ,則將集合包裹成 zepto 對象全部返回,否則調用 filter 方法,過濾出符合條件的元素返回。filter 方法下麵馬上講到。

元素方法

這裡的方法都是 $.fn 中提供的方法。

.filter()

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()

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

selectorFunction ,並且有 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)

selectorstring 時,調用 filter ,找出所有需要排除的元素

(likeArray(selector) && isFunction(selector.item)) ? slice.call(selector) : $(selector)

這段我剛開始看時,有點困惑,主要是不明白 isFunction(selector.item) 這個判斷條件,後來查了MDN文檔HTMLCollection.item,才明白 itemHTMLCollection 的一個方法,這個三元表達式的意思是,如果是 HTMLCollection ,則調用 slice.call 得到一個純數組,否則返回 zepto 對象。

this.forEach(function(el) {
    if (excludes.indexOf(el) < 0) nodes.push(el)
})

遍歷集合,如果元素不在需要排除的元素集合中,將該元素 pushnodes 中。

not 方法最終返回的也是 zepto 對象。

.is()

is: function(selector) {
  return this.length > 0 && zepto.matches(this[0], selector)
}

判斷集合中的第一個元素是否匹配指定的選擇器。

代碼也比較簡單了,選判斷集合不為空,再調用 matches 看第一個元素是否匹配。

.find()

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 對象collectiondom 節點 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()

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()

eq: function(idx) {
  return idx === -1 ? this.slice(idx) : this.slice(idx, +idx + 1)
},

獲取集合中指定的元素。

這裡調用了 slice 方法,這個方法在上一篇《讀Zepto源碼之集合操作》已經說過了。如果 idx-1 時,直接調用 this.slice(idx) ,即取出最後一個元素,否則取 idxidx + 1 之間的元素,也就是每次只取一個元素。+idx+1 前面的 + 號其實是類型轉換,確保 idx 在做加法的時候為 Number 類型。

.first()

first: function() {
  var el = this[0]
  return el && !isObject(el) ? el : $(el)
},

first 是取集合中第一個元素,這個方法很簡單,用索引 0 就可以取出來了,也就是 this[0]

el && !isObject(el) 用來判斷是否為 zepto 對象,如果不是,用 $(el) 包裹,確保返回的是 zepto 對象。

.last()

last: function() {
  var el = this[this.length - 1]
  return el && !isObject(el) ? el : $(el)
},

last 是取集合中最後一個元素,這個的原理跟 first 一樣,只不過變成了取索引值為 this.length - 1 ,也就是最後的元素。

.closest()

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 是否為 collectionelement ,如果是,則統一轉化為 zepto 集合。

然後對集合遍歷,在 each 遍歷里針對集合中每個 node 節點,都用 while 語句,向上查找符合條件的元素。

node && !(collection ? collection.indexOf(node) >= 0 : zepto.matches(node, selector))

這段是 while 語句的終止條件。 node 節點必須存在,如果 selectorzepto 集合或者 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()

pluck: function(property) {
  return $.map(this, function(el) { return el[property] })
},

返回集合中所有元素指定的屬性值。

這個方法很簡單,就是對當前集合遍歷,然後取元素指定的 property 值。

.parents()

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()

parent: function(selector) {
  return filtered(uniq(this.pluck('parentNode')), selector)
},

返回集合中所有元素的父級元素。

parents 返回的是所有祖先元素,而 parent 返回只是父級元素。

首先調用的是 this.pluck('parentNode') ,獲取所有元素的祖先元素,然後調用 uniq 對集合去重,最後調用 filtered ,返回匹配 selector 的元素集合。

.children()

children: function(selector) {
  return filtered(this.map(function() { return children(this) }), selector)
},

返回集合中所有元素的子元素。

首先對當前集合遍歷,調用內部方法 children 獲取當前元素的子元素組成新的數組,再調用 filtered 方法返回匹配 selector 的元素集合。

.contents()

contents: function() {
  return this.map(function() { return this.contentDocument || slice.call(this.childNodes) })
},

這個方法類似於 children ,不過 childrenchildNodes 進行了過濾,只返回元素節點。contents 還返迴文本節點和註釋節點。也返回 iframecontentDocument

.siblings()

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()

prev: function(selector) { return $(this.pluck('previousElementSibling')).filter(selector || '*') },

獲取集合中每個元素的前一個兄弟節點。

這個方法也很簡單,調用 pluck 方法,獲取元素的 previousElementSibling 屬性,即為元素的前一個兄弟節點。再調用 filter 返回匹配 selector 的元素,最後包裹成 zepto 對象返回

.next()

next: function(selector) { return $(this.pluck('nextElementSibling')).filter(selector || '*') },

next 方法跟 prev 方法類似,只不過取的是 nextElementSibling 屬性,獲取的是每個元素的下一個兄弟節點。

.index()

index: function(element) {
  return element ? this.indexOf($(element)[0]) : this.parent().children().indexOf(this[0])
},

返回指定元素在當前集合中的位置(this.indexOf($(element)[0])),如果沒有給出 element ,則返回當前鮮紅在兄弟元素中的位置。this.parent().children() 查找的是兄弟元素。

系列文章

  1. 讀Zepto源碼之代碼結構
  2. 讀 Zepto 源碼之內部方法
  3. 讀Zepto源碼之工具函數
  4. 讀Zepto源碼之神奇的$
  5. 讀Zepto源碼之集合操作

參考

License

License: CC BY-NC-ND 4.0

作者:對角另一面


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 今天這一題有點燒腦: 有一個序列u,滿足: 1. 第一個元素是1 2. 此後任意一個元素x,2x+1和3x+1也必定在u中 現給定整數n,求序列u中的第n+1個元素是什麼? 規定:要註意演算法的效率 分析: 乍一想有點亂。先找幾個數計算一下: 1 [1], 3, 4 1, [3], 4, 7, 10 ...
  • 英文分詞的第三方庫NLTK不錯,中文分詞工具也有很多(盤古分詞、Yaha分詞、Jieba分詞等)。但是從載入自定義字典、多線程、自動匹配新詞等方面來看。 大jieba 確實是中文分詞中的 戰鬥機 。 請隨意觀看表演 "安裝" "分詞" "自定義詞典" "延遲載入" "關鍵詞提取" "詞性標註" "詞 ...
  • ID3決策樹實現源碼(Python版),機器學習經典演算法起步階段,歡迎討論交流。 ...
  • 視頻在youtube網站國內訪問不了,可以使用翻牆軟體查看。 視頻地址:www.youtube.com/embed/682p52tFcmY@autoplay=1 下麵是視頻文字介紹: Magento 2系統整體結構是建立在模塊的基礎上。通常,創建定製的第一步是構建模塊。 要創建模塊,需要完成以下步驟 ...
  • 0 目錄 認證授權系列:http://www.cnblogs.com/linianhui/category/929878.html 1 什麼是OIDC? 看一下官方的介紹(http://openid.net/connect/): OpenID Connect 1.0 is a simple iden ...
  • 1.angular 調用客戶端方法放在 try catch中 try { js_invoke.showShareDialog(angular.toJson(obj)); // 在這裡放客戶端的方法即可 } catch(e) { console.log('Recommend share',obj); ...
  • jq筆記-dom篇-慕課網學習筆記 1.jQuery節點創建與屬性的處理 創建元素節點: 1.$("<div></div>") 創建為本節點: 1.$("<div>我是文本節點</div>") 創建為屬性節點: 1.$("<div id='test' class='aaron'>我是文本節點</di ...
  • 學完了Javascript類和對象的創建之後,現在總結一下Javascript繼承機制的實現。Javascript並不像Java那樣對繼承機制有嚴格明確的定義,它的實現方式正如它的變數的使用方式那樣也是十分寬鬆的,你可以設計自己的方法“模仿”繼承機制的實現。有以下幾種方法: 1、對象冒充 1 <sc ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...