之前咱们使用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模块