Zepto
的 Data
模塊用來獲取 DOM
節點中的 data-*
屬性的數據,和儲存跟 DOM
相關的數據。
讀 Zepto 源碼系列文章已經放到了github上,歡迎star: reading-zepto
源碼版本
本文閱讀的源碼為 zepto1.2.0
GitBook
內部方法
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)
首先讀取 node
的 exp
屬性,從前面可以看到 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
節點的緩存數據,最終分別調用的是 setData
和 getData
方法。
分析這段代碼,照例還是將三元表達式一個一個拆解,來看看都做了什么事情。
value === undefined ? 三元表達式 : this.each(function(){ setData(this, name, value) })
先看第一層,當有傳遞 name
和 value
時,表明是設置緩存,遍歷所有元素,分別調用 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)
}
})
原有的 remove
和 empty
方法,都會有 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
最后調用的也就是 DOM
的 data
方法。
$.hasData
$.hasData = function(elem) {
var id = elem[exp], store = id && data[id]
return store ? !$.isEmptyObject(store) : false
}
判斷某個元素是否已經有緩存的數據。
首先通過從緩存 data
中,取出對應 DOM
的緩存 store
,如果 store
存在,并且不為空,則返回 true
,其實情況返回 false
。
系列文章
- 讀Zepto源碼之代碼結構
- 讀Zepto源碼之內部方法
- 讀Zepto源碼之工具函數
- 讀Zepto源碼之神奇的$
- 讀Zepto源碼之集合操作
- 讀Zepto源碼之集合元素查找
- 讀Zepto源碼之操作DOM
- 讀Zepto源碼之樣式操作
- 讀Zepto源碼之屬性操作
- 讀Zepto源碼之Event模塊
- 讀Zepto源碼之IE模塊
- 讀Zepto源碼之Callbacks模塊
- 讀Zepto源碼之Deferred模塊
- 讀Zepto源碼之Ajax模塊
- 讀Zepto源碼之Assets模塊
- 讀Zepto源碼之Selector模塊
- 讀Zepto源碼之Touch模塊
- 讀Zepto源碼之Gesture模塊
- 讀Zepto源碼之IOS3模塊
- 讀Zepto源碼之Fx模塊
- 讀Zepto源碼之fx_methods模塊
- 讀Zepto源碼之Stack模塊
- 讀Zepto源碼之Form模塊
附文
參考
License
署名-非商業性使用-禁止演繹 4.0 國際 (CC BY-NC-ND 4.0)
最后,所有文章都會同步發送到微信公眾號上,歡迎關注,歡迎提意見:作者:對角另一面