Zepto
的 Data
模塊用來獲取 DOM
節點中的 data-*
屬性的數據,和儲存跟 DOM
相關的數據。javascript
讀 Zepto 源碼系列文章已經放到了github上,歡迎star: reading-zeptojava
本文閱讀的源碼爲 zepto1.2.0node
《reading-zepto》git
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源碼之屬性操做》緩存
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複製代碼
最後,設置須要緩存的值。
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-
屬性上的值,這個方法後面會分析到。
$.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
的緩存。
$.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'].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 = function(elem, name, value) {
return $(elem).data(name, value)
}複製代碼
data
最後調用的也就是 DOM
的 data
方法。
$.hasData = function(elem) {
var id = elem[exp], store = id && data[id]
return store ? !$.isEmptyObject(store) : false
}複製代碼
判斷某個元素是否已經有緩存的數據。
首先經過從緩存 data
中,取出對應 DOM
的緩存 store
,若是 store
存在,而且不爲空,則返回 true
,其實狀況返回 false
。
署名-非商業性使用-禁止演繹 4.0 國際 (CC BY-NC-ND 4.0)
最後,全部文章都會同步發送到微信公衆號上,歡迎關注,歡迎提意見:
做者:對角另外一面