Zepto 的 Data 模塊用來獲取 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)
首先讀取 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模塊
附文
- 譯:怎樣處理 Safari 移動端對圖片資源的限制
參考
- Zepto中數據緩存原理與實現
- Element.attributes
License
署名-非商業性使用-禁止演繹 4.0 國際 (CC BY-NC-ND 4.0)
最後,所有文章都會同步發送到微信公眾號上,歡迎關注,歡迎提意見:
作者:對角另一面