讀Zepto源碼之Data模塊

ZeptoData 模塊用來獲取 DOM 節(jié)點中的 data-* 屬性的數(shù)據(jù),和儲存跟 DOM 相關的數(shù)據(jù)。

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

源碼版本

本文閱讀的源碼為 zepto1.2.0

GitBook

reading-zepto

內(nèi)部方法

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 獲取到的是節(jié)點的所有屬性,因此在遍歷的時候,需要判斷屬性名是否以 data- 開頭。

在存儲的時候,將屬性名的 data- 去掉,剩余部分轉(zhuǎn)換成駝峰式,作為 store 對象的 key

DOM 中的屬性值都為字符串格式,為方便操作,調(diào)用 deserializeValue 方法,轉(zhuǎn)換成對應的數(shù)據(jù)類型,關于這個方法的具體分析,請看 《讀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
}

更多時候,儲存數(shù)據(jù)不需要寫在 DOM 中,只需要儲存在內(nèi)存中即可。而且讀取 DOM 的成本非常高。

setData 方法會將對應 DOM 的數(shù)據(jù)儲存在 store 對象中。

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

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

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

data 中獲取節(jié)點之前緩存的數(shù)據(jù),如果之前沒有緩存數(shù)據(jù),則調(diào)用 attributeData 方法,獲取節(jié)點上所有以 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 節(jié)點指定的緩存值。

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

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

if (name in store) return store[name]

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

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

否則,將指定的 name 轉(zhuǎn)換成駝峰式,再從緩存 store 中查找,將找到的結果返回。這是兼容 camel-name 這樣的參數(shù)形式,提供更靈活的 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 節(jié)點的緩存數(shù)據(jù),最終分別調(diào)用的是 setDatagetData 方法。

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

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

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

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

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

因此,遍歷所有元素,調(diào)用 setData 設置緩存。

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

最后,判斷集合是否不為空( 0 in this ), 如果為空,則直接返回 undefined ,否則,調(diào)用 getData ,返回第一個元素節(jié)點對應 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 用來刪除緩存的數(shù)據(jù),如果沒有傳遞參數(shù),則全部清空,如果有傳遞參數(shù),則只刪除指定的數(shù)據(jù)。

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

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

如果檢測到 names 為字符串,則先將字符串轉(zhuǎn)換成數(shù)組。

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 存在,則刪除指定的數(shù)據(jù),否則將 store 緩存的數(shù)據(jù)全部刪除。

.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 節(jié)點的移除,在移除 DOM 節(jié)點后,對應節(jié)點的緩存數(shù)據(jù)也就沒有什么意義了,所有在移除 DOM 節(jié)點后,也需要將節(jié)點對應的數(shù)據(jù)也清空,以釋放內(nèi)存。

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

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

最后調(diào)用 removeData 方法,不傳參清空所有數(shù)據(jù),在清空數(shù)據(jù)后,再調(diào)用原來的方法移除節(jié)點。

工具方法

$.data

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

data 最后調(diào)用的也就是 DOMdata 方法。

$.hasData

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

判斷某個元素是否已經(jīng)有緩存的數(shù)據(jù)。

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

系列文章

  1. 讀Zepto源碼之代碼結構
  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源碼之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

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

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

作者:對角另一面

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容