讀Zepto原始碼之Data模組

對角另一面發表於2017-10-25

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模組

附文

參考

License

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

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

作者:對角另一面

相關文章