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)
最後,所有文章都會同步傳送到微信公眾號上,歡迎關注,歡迎提意見:
作者:對角另一面