博客 / 詳情

返回

讀Zepto源碼之Data模塊

ZeptoData 模塊用來獲取 DOM 節點中的 data-* 屬性的數據,和儲存跟 DOM 相關的數據。

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

源碼版本

本文閲讀的源碼為 zepto1.2.0

GitBook

《reading-zepto》

內部方法

attributeData

var data = {}, dataAttr = $.fn.data, camelize = $.camelCase,
    exp = $.expando = 'Zepto' + (+new Date()), emptyArray = []
function attributeData(node) {
  var store = {}
  $.each(node.attributes || emptyArray, function(i, attr){
    if (attr.name.indexOf('data-') == 0)
      store[camelize(attr.name.replace('data-', ''))] =
        $.zepto.deserializeValue(attr.value)
  })
  return store
}

這個方法用來獲取給定 node 中所有 data-* 屬性的值,並儲存到 store 對象中。

node.attributes 獲取到的是節點的所有屬性,因此在遍歷的時候,需要判斷屬性名是否以 data- 開頭。

在存儲的時候,將屬性名的 data- 去掉,剩餘部分轉換成駝峯式,作為 store 對象的 key

DOM 中的屬性值都為字符串格式,為方便操作,調用 deserializeValue 方法,轉換成對應的數據類型,關於這個方法的具體分析,請看 《讀Zepto源碼之屬性操作》

setData

function setData(node, name, value) {
  var id = node[exp] || (node[exp] = ++$.uuid),
      store = data[id] || (data[id] = attributeData(node))
  if (name !== undefined) store[camelize(name)] = value
  return store
}

更多時候,儲存數據不需要寫在 DOM 中,只需要儲存在內存中即可。而且讀取 DOM 的成本非常高。

setData 方法會將對應 DOM 的數據儲存在 store 對象中。

var id = node[exp] || (node[exp] = ++$.uuid)

首先讀取 nodeexp 屬性,從前面可以看到 exp 是一個 Zepto 加上時間戳的字符串,以確保屬性名的唯一性,避免覆蓋用户自定義的屬性,如果 node 尚未打上 exp 標記,表明這個節點並沒有緩存的數據,則設置節點的 exp 屬性。

store = data[id] || (data[id] = attributeData(node))

data 中獲取節點之前緩存的數據,如果之前沒有緩存數據,則調用 attributeData 方法,獲取節點上所有以 data- 開頭的屬性值,緩存到 data 對象中。

store[camelize(name)] = value

最後,設置需要緩存的值。

getData

function getData(node, name) {
  var id = node[exp], store = id && data[id]
  if (name === undefined) return store || setData(node)
  else {
    if (store) {
      if (name in store) return store[name]
      var camelName = camelize(name)
      if (camelName in store) return store[camelName]
    }
    return dataAttr.call($(node), name)
  }
}

獲取 node 節點指定的緩存值。

if (name === undefined) return store || setData(node)

如果沒有指定屬性名,則將節點對應的緩存全部返回,如果緩存為空,則調用 setData 方法,返回 node 節點上所有以 data- 開頭的屬性值。

if (name in store) return store[name]

如果指定的 name 在緩存 store 中,則將結果返回。

var camelName = camelize(name)
if (camelName in store) return store[camelName]

否則,將指定的 name 轉換成駝峯式,再從緩存 store 中查找,將找到的結果返回。這是兼容 camel-name 這樣的參數形式,提供更靈活的 API

如果緩存中都沒找到,則回退到用 $.fn.data 查找,其實就是查找 data- 屬性上的值,這個方法後面會分析到。

DOM方法

.data()

$.fn.data = function(name, value) {
  return value === undefined ?
    $.isPlainObject(name) ?
    this.each(function(i, node){
    $.each(name, function(key, value){ setData(node, key, value) })
  }) :
  (0 in this ? getData(this[0], name) : undefined) :
  this.each(function(){ setData(this, name, value) })
}

data 方法可以設置或者獲取對應 node 節點的緩存數據,最終分別調用的是 setDatagetData 方法。

分析這段代碼,照例還是將三元表達式一個一個拆解,來看看都做了什麼事情。

value === undefined ? 三元表達式 : this.each(function(){ setData(this, name, value) })

先看第一層,當有傳遞 namevalue 時,表明是設置緩存,遍歷所有元素,分別調用 setData 方法設置緩存。

$.isPlainObject(name) ?
    this.each(function(i, node){
    $.each(name, function(key, value){ setData(node, key, value) })
  }) : 三元表達式

data 的第一個參數還支持對象的傳值,例如 $(el).data({key1: 'value1'}) 。如果是對象,則對象裏的屬性為需要設置的緩存名,值為緩存值。

因此,遍歷所有元素,調用 setData 設置緩存。

0 in this ? getData(this[0], name) : undefined

最後,判斷集合是否不為空( 0 in this ), 如果為空,則直接返回 undefined ,否則,調用 getData ,返回第一個元素節點對應 name 的緩存。

.removeData()

$.fn.removeData = function(names) {
  if (typeof names == 'string') names = names.split(/\s+/)
  return this.each(function(){
    var id = this[exp], store = id && data[id]
    if (store) $.each(names || store, function(key){
      delete store[names ? camelize(this) : key]
    })
  })
}

removeData 用來刪除緩存的數據,如果沒有傳遞參數,則全部清空,如果有傳遞參數,則只刪除指定的數據。

names 可以為數組,指定需要刪除的一組數據,也可以為以空格分割的字符串。

if (typeof names == 'string') names = names.split(/\s+/)

如果檢測到 names 為字符串,則先將字符串轉換成數組。

return this.each(function(){
  var id = this[exp], store = id && data[id]
 ...
})

遍歷元素,對所有的元素都進行刪除操作,找出和元素對應的緩存 store

if (store) $.each(names || store, function(key){
  delete store[names ? camelize(this) : key]
})

如果 names 存在,則刪除指定的數據,否則將 store 緩存的數據全部刪除。

.remove()和.empty()方法的改寫

;['remove', 'empty'].forEach(function(methodName){
  var origFn = $.fn[methodName]
  $.fn[methodName] = function() {
    var elements = this.find('*')
    if (methodName === 'remove') elements = elements.add(this)
    elements.removeData()
    return origFn.call(this)
  }
})

原有的 removeempty 方法,都會有 DOM 節點的移除,在移除 DOM 節點後,對應節點的緩存數據也就沒有什麼意義了,所有在移除 DOM 節點後,也需要將節點對應的數據也清空,以釋放內存。

var elements = this.find('*')
if (methodName === 'remove') elements = elements.add(this)

elements 為所有下級節點,如果為 remove 方法,則節點自身也是要被移除的,所以需要將自身也加入到節點中。

最後調用 removeData 方法,不傳參清空所有數據,在清空數據後,再調用原來的方法移除節點。

工具方法

$.data

$.data = function(elem, name, value) {
  return $(elem).data(name, value)
}

data 最後調用的也就是 DOMdata 方法。

$.hasData

$.hasData = function(elem) {
  var id = elem[exp], store = id && data[id]
  return store ? !$.isEmptyObject(store) : false
}

判斷某個元素是否已經有緩存的數據。

首先通過從緩存 data 中,取出對應 DOM 的緩存 store ,如果 store 存在,並且不為空,則返回 true ,其實情況返回 false

系列文章

  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模塊
  16. 讀Zepto源碼之Selector模塊
  17. 讀Zepto源碼之Touch模塊
  18. 讀Zepto源碼之Gesture模塊
  19. 讀Zepto源碼之IOS3模塊
  20. 讀Zepto源碼之Fx模塊
  21. 讀Zepto源碼之fx_methods模塊
  22. 讀Zepto源碼之Stack模塊
  23. 讀Zepto源碼之Form模塊

附文

  • 譯:怎樣處理 Safari 移動端對圖片資源的限制

參考

  • Zepto中數據緩存原理與實現
  • Element.attributes

License

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

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

作者:對角另一面

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.