之前咱們使用Zepto進行開發的時候,會把一些自定義的數據存到dom節點上,好處是很是直觀和便捷,可是也帶來了例如直接將數據暴露出來會出現安全問題,數據以html自定義屬性標籤存在,對於瀏覽器自己來講是沒有多大意義的,最後要獲取數據的時候還得操做dom。Zepto有一個
data
模塊,專門用來作數據緩存,容許咱們存聽任何與dom相關的數據。javascript
原文連接html
源碼倉庫java
在開始學習和閱讀Zepto中的data模塊前,咱們先大體瞭解一下dom元素和要緩存的數據是如何聯繫起來的。node
看一下上面那張圖。簡單地理解就是git
exp(Zepto1507010934916)
屬性,其對應的值是1,2,3整數數字,{ 1: { name: 'qianlongo' }, 2: { sex: 'boy' } }
在匹配元素上存儲任意相關數據或返回匹配的元素集合中的第一個元素的給定名稱的數據存儲的值。github
例子ajax
<div class="box" data-name="qianlongo" data-sex="boy"></div>
let $box = $('.box') // setData $box.data("foo", 52) $box.data("bar", { myType: "test", count: 40 }) $box.data({ baz: [ 1, 2, 3 ] }) // getData $box.data("foo") // 52 $box.data("name") // qianlongo $box.data() // { name: "qianlongo", sex: "boy", foo: 52, bar: { myType: "test", count: 40 }, baz: [ 1, 2, 3 ] }
基本用法你們確定很熟悉,須要注意的地方是,咱們也能夠直接獲取定義在html標籤上以data-
爲前綴的屬性。接下來咱們就直接看源碼實現啦json
源碼數組
$.fn.data = function(name, value) { return value === undefined ? // set multiple values via object $.isPlainObject(name) ? this.each(function(i, node){ $.each(name, function(key, value){ setData(node, key, value) }) }) : // get value from first element (0 in this ? getData(this[0], name) : undefined) : // set value on all elements this.each(function(){ setData(this, name, value) }) }
經過上面的例子咱們知道,設置數據的時候能夠單個屬性設置,也能夠多個屬性(傳遞一個對象)一塊兒設置。大量使用三目運算是Zepto一向的風格。咱們來拆解一下這段代碼。瀏覽器
this.each(function(){ setData(this, name, value) })
經過遍歷匹配元素,並調用setData方法傳入元素,要設置的數據的key和value。
$box.data({ baz: [ 1, 2, 3 ] })
此時走的是這段代碼
this.each(function(i, node){ $.each(name, function(key, value){ setData(node, key, value) }) })
仍是遍歷當前匹配元素,而且遍歷傳進的對象name
,到底層仍是調用setData
方法一個個屬性進行設置。
(0 in this ? getData(this[0], name) : undefined)
經過判斷當前是否有匹配的元素,若是有則是調用getData
方法,並傳入匹配元素集合中的第一個元素,以及要獲取的數據name屬性。若是沒有匹配元素,就直接返回undefined了。
整體邏輯仍是挺清晰的。接下來咱們主要須要弄清楚上面用到的幾個函數setData
,getData
。以及解釋一下data模塊初始定義的幾個變量
var data = {}, dataAttr = $.fn.data, camelize = $.camelCase, exp = $.expando = 'Zepto' + (+new Date())
各變量解釋以下
/** * data 存儲於dom相映射的數據數據結構如同下 * { * 1: { * name: 'qianlongo', * sex: 'boy' * }, * 2: { * age: 100 * } * } * * dataAttr $原型上的data方法,經過getAttribute和setAttribute設置或讀取元素屬性 * camelize 中劃線轉小駝峯函數 * exp => Zepto1507004986420 設置在dom上的屬性,value是data中的key 1, 2,3等 */
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 }
exp
是相似Zepto1507004986420
的字符串,$.uuid初始值是0,首先會嘗試去讀取元素身上的exp屬性,元素沒有該屬性就爲該元素設置exp
屬性。
並去data
大對象中讀取id(1, 2, 3...)
屬性,固然了若是data對象中沒有讀取到,就經過調用attributeData
函數先獲取node
節點全部以data-
爲前綴的自定義屬性,並將其賦值。
如今自定義屬性的集合已經有了,先判斷name是不是個undefined,不是就往store上添加name屬性。
最後函數調用以後會返回整個數據對象store。
attributeData
獲取元素以data-
爲前綴的自定義屬性的集合
// Read all "data-*" attributes from a node 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.attributes mdn是個啥
Element.attributes 屬性返回該元素全部屬性節點的一個實時集合。該集合是一個 NamedNodeMap 對象,不是一個數組,因此它沒有 數組 的方法,其包含的 屬性 節點的索引順序隨瀏覽器不一樣而不一樣。更確切地說,attributes 是字符串形式的名/值對,每一對名/值對對應一個屬性節點。
例子
<div class="box" data-name="qianlongo" data-sex="boy" foo="foo" title="標題"></div>
let $box = document.querySelector('.box') $box.dataset.age = 100 console.log($box.attributes)
獲得的數據如上圖所示,接下來咱們再回到attributeData
函數的源碼分析
if (attr.name.indexOf('data-') == 0) store[camelize(attr.name.replace('data-', ''))] = $.zepto.deserializeValue(attr.value)
經過判斷ele.attributes
拿到的集合中,是不是以data-
開頭的屬性,若是是就往store對象中添加駝峯化後的該屬性,而且序列化以後的attr.value
做爲該屬性的值。最後將store對象返回。
getData
獲取存儲在data中與DOM元素關聯的對象name屬性。當name屬性不存在的時候直接返回整個對象。
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) } }
實現思路仍是首先去讀取setData
時候添加在node節點上的id,而後以該id爲key去data中查找。若是name沒有傳,此時直接返回整個store,固然若是store也沒有找到,就返回調用setData
後返回的該元素的自定義屬性的集合。
當store存在時,先判斷name屬性在store中存在與否,存在便直接返回相應的屬性,不然對傳入的name進行駝峯化以後再判斷在store中是否存在,存在即返回對應的屬性。也就是說你傳入的name爲min-age
或者minAge
獲得的是同樣的值。
最後若是在數據緩存中尚未找到屬性name,就調用dataAttr函數,去直接查找元素身上的相關屬性。
在元素上移除綁定的數據
能夠添加或者更新數據天然也就能夠移除數據了,先看下例子
例子
<div class="box"></div>
let $box = $('.box') $box.data("foo", 52) $box.data("bar", { myType: "test", count: 40 }) $box.data({ baz: [ 1, 2, 3 ] }) // $box.removeData('foo') // $box.removeData('foo bar baz') // $box.removeData(['foo', 'bar', 'baz']) // $box.removeData()
咱們能夠指定刪除單個屬性,也能夠經過空格隔開刪除多個屬性,也能夠傳入一個要刪除的屬性數組,甚至當你什麼都不傳的時候,原先設置在該元素身上的data會被所有清空
源碼
$.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] }) }) }
首先傳進來的names是字符串的狀況下,先轉化成數組,接着就是對當前匹配的元素集合進行遍歷,逐個刪除元素對應的緩存的數據。
當查找到store的時候對轉化後的names或者store進行遍歷,若是是本身指定要刪除的屬性,先駝峯化一下,再用delete刪除,不然所有清空則直接delete store中的key
存儲任意數據到指定的元素而且/或者返回設置的值
$.data = function(elem, name, value) { return $(elem).data(name, value) }
定義在$函數身上的靜態方法,底層仍是調用的實例方法.data。
肯定元素是否有與之相關的Zepto數據。
$.hasData = function(elem) { var id = elem[exp], store = id && data[id] return store ? !$.isEmptyObject(store) : false }
一樣定義在$函數身上的靜態方法,原理就是拿着elem
身上的id,去data中查找是否有與之關聯的數據對象,若是找到了而且不是一個空對象,便返回true,不然沒有找到或者是空對象都是返回false
生成擴展的remove和empty方法,未擴展以前的remove和empty功能依舊還在,增添了刪除選中的元素緩存的數據功能。
;['remove', 'empty'].forEach(function(methodName){ // 緩存原型上以前對應的remove和empty方法 var origFn = $.fn[methodName] // 重寫兩個方法 $.fn[methodName] = function() { // 獲取當前選中元素的全部內部包含元素 var elements = this.find('*') // 若是是remove方法,則在獲取的elements元素基礎上把自己也添加進去 if (methodName === 'remove') elements = elements.add(this) // 調用removeData刪除與dom關聯的data中的數據 elements.removeData() // 最後仍是調用對應的方法刪除dom,或者清除dom的內容 return origFn.call(this) } })
以上是Zepto種data模塊全部源碼分析,歡迎你們指正其中有問題的地方。
文章記錄
data模塊
form模塊
zepto模塊
event模塊
ajax模塊