讀Zepto源碼之Data模塊

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

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

源碼版本

本文閱讀的源碼爲 zepto1.2.0node

GitBook

reading-zeptogit

內部方法

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 對象中。github

node.attributes 獲取到的是節點的全部屬性,所以在遍歷的時候,須要判斷屬性名是否以 data- 開頭。segmentfault

在存儲的時候,將屬性名的 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)

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

做者:對角另外一面

相關文章
相關標籤/搜索