讀Zepto源碼之Data模塊

ZeptoData 模塊用來獲取 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)

首先讀取 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)

最后,所有文章都會同步發送到微信公眾號上,歡迎關注,歡迎提意見:

作者:對角另一面

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,106評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,441評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,211評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,736評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,475評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,834評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,829評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,009評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,559評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,306評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,516評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,038評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,728評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,132評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,443評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,249評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,484評論 2 379

推薦閱讀更多精彩內容