Zepto中数据缓存的原理与实现
时间:2023-04-05 16:21:24
HTML5
前言我们在使用Zepto进行开发的时候,会在dom节点上存储一些自定义的数据。优点是非常直观方便,但也带来了数据直接暴露等问题。出来的时候会有安全问题。数据以html自定义属性标签的形式存在,对浏览器本身没有意义。最后,想要获取数据的时候,还得操作dom。Zepto有一个数据模块,专门用于数据缓存,让我们可以存储任何与dom相关的数据。原文链接源码仓库原理在我们开始学习和阅读Zepto中的数据模块之前,我们先大致了解一下dom元素和要缓存的数据是如何关联的。看看上面的图片。简单理解就是在dom元素上有一个exp(Zepto1507010934916)属性,它对应的值为整数1、2、3,data是一个大对象,存储与dom元素关联的自定义数据,类似到下面的{1:{name:'qianlongo'},2:{sex:'boy'}}DOM元素通过1,2,3数字索引关联到大对象数据。DOM自定义数据的增删改查是对应数字索引对象进行操作的。$.fn.data存储匹配元素上的任何关联数据,或返回匹配元素集中第一个元素的给定名称的数据存储值。示例
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]}你必须熟悉基本用法。需要注意的是,我们也可以直接获取以data-为前缀的html标签上定义的属性。接下来我们直接看源码实现。源代码$.fn.data=function(name,value){返回值===undefined?//通过对象设置多个值$.isPlainObject(name)?this.each(function(i,node){$.each(name,function(key,value){setData(node,key,value)})})://从第一个元素获取值(0inthis?getData(this[0],name):undefined)://给所有元素设置值this.each(function(){setData(this,name,value)})}通过上面的例子我们知道,在设置数据的时候,我们可以设置单个属性或多个属性(传递一个对象)一起设置。大量使用三目操作是Zepto一贯的风格。让我们反汇编这段代码。当value传递一个值并且不是undefined时,可以认为是设置了一个单一的数据属性。所以拿这段代码this.each(function(){setData(this,name,value)})遍历匹配的元素,调用setData方法传入元素,要设置的数据的key和value。当没有传入任何值且name是一个纯对象时。也就是说,像这样使用$box.data({baz:[1,2,3]})就是代码this.each(function(i,node){$.each(name,function(key,value)){setData(node,key,value)})})还是遍历当前匹配的元素,并遍历传入的对象名,在底层调用setData方法一一设置属性。当name不是对象时,它被认为是对数据的读取操作。这段代码(0inthis?getData(this[0],name):undefined)用于判断当前是否有匹配元素,如果有则调用getData方法,传入匹配元素集合中的第一个元素,以及要获取的数据名称属性。如果没有匹配到的元素,则直接返回undefined。整体逻辑还是很清晰的。接下来我们主要是搞清楚上面用到的函数setData和getData。并解释数据模块最初定义的变量vardata={},dataAttr=$.fn.data,camelize=$.camelCase,exp=$.expando='Zepto'+(+newDate())各个变量的解释如下/***dom中存储的数据的数据结构如下*{*1:{*name:'qianlongo',*sex:'boy'*},*2:{*age:100*}*}**dataAttr$prototype上的data方法,通过getAttribute和setAttribute设置或读取元素属性*驼峰化下划线为小驼峰函数*exp=>Zepto1507004986420设置dom上的属性,值为数据中的键1、2、3等*/setDatafunctionsetData(node,name,value){varid=node[exp]||(node[exp]=++$.uuid),store=data[id]||(data[id]=attributeData(node))if(name!==undefined)store[camelize(name)]=valuereturnstore}exp是一个类似于Zepto1507004986420的字符串,$.uuid的初始值为0,并且它会先尝试读取元素上的exp属性,如果元素没有这个属性,则为元素设置exp属性。并到数据对象中读取id(1,2,3...)属性。当然,如果不是读入data对象,首先通过调用attributeData函数属性获取所有以data-为前缀的自定义node节点,并为其赋值。既然有了自定义属性的集合,首先判断name是不是undefined,或者把name属性添加到store中。最后一次函数调用后,将返回整个数据对象存储。attributeData获取前缀为data-//从节点中读取所有“data-*”属性的集合functionattributeData(node){varstore={}$.each(node.attributes||emptyArray,function(i,attr){if(attr.name.indexOf('data-')==0)store[camelize(attr.name.replace('data-',''))]=$.zepto.deserializeValue(attr.value)})returnstore}我们先看看node.attributesmdn是什么。Element.attributes属性返回元素所有属性节点的实时集合。该集合是一个NamedNodeMap对象,不是一个数组,所以它没有数组方法,它包含的属性节点的索引顺序因浏览器而异。更准确地说,属性是字符串形式的名称/值对,每一对对应一个属性节点。示例
让$box=document.querySelector('.box')$box.dataset.age=100console.log($box.attributes)得到的数据如上图所示,接下来我们回到attributeData函数的源码分析if(attr.name.indexOf('data-')==0)store[camelize(attr.name.replace('data-',''))]=$.zepto.deserializeValue(attr.value)判断ele.attributes得到的set是否为基础ondata-开头的属性,如果是,则在store对象中添加驼峰化的属性,序列化的attr.value作为属性的值。最后,返回商店对象。getData获取与存储在data中的DOM元素关联的对象名称属性。当name属性不存在时,直接返回整个对象。functiongetData(node,name){varid=node[exp],store=id&&data[id]if(name===undefined)返回存储||setData(node)else{if(store){if(nameinstore)returnstore[name]varcamelName=camelize(name)if(camelNameinstore)返回store[camelName]}returndataAttr.call($(node),name)}}实现思路是先读取setData,在node节点上添加id,然后以id为key在数据中查找。如果不传名字,此时会直接退回整个店铺。当然如果没有找到store,会返回调用setData后返回的元素的自定义属性集合。当store存在时,首先判断store中是否存在name属性,存在则直接返回对应的属性。否则,对传入的名称进行驼峰处理,然后判断store中是否存在,存在则返回对应的属性。也就是说,如果你传入的名字是min-age或者minAge,你得到的值都是一样的。最后,如果在数据缓存中没有找到属性名,则调用dataAttr函数直接在元素上查找相关属性。removeData移除元素上绑定的数据,可以添加或更新数据,自然也可以移除数据,先看例子