jQuery 数据缓存模块进化史详细介绍

数据缓存系统最早应该是jQuery1.2引入的,那时它的事件系统完成照搬DE大神的addEvent.js,而addEvent在实现有个缺憾,它把事件的回调都放到EventTarget之上,这会引发循环引用,如果EventTarget是window对象,又会引发全局污染。有了数据缓存系统,除了规避这两个风险外,我们还可以有效地保存不同方法产生的中间变量,而这些变量会对另一个模块的方法有用,解耦方法间的依赖。对于jQuery来说,它的事件克隆乃至后来的列队实现都是离不开缓存系统。

jQuery1.2 在core模块新增了两个静态方法, data与removeData。data不用说,与jQuery其他方法一样,读写结合。jQuery的缓存系统是把所有数据都放$.cache之上,然后为每个要使用缓存系统的元素节点,文档对象与window对象分配一个UUID。UUID的属性名为一个随机的自定义属性,"jQuery" + (new Date()).getTime(), 值为整数,从零递增。但UUID总要附于一个对象上,如果那个对象是window,岂不是全局污染吗,因此jQuery内部判定它是window对象时,映射为一个叫windowData的空对象,然后UUID加在它之上。有了UUID,我们在首次访问缓存系统时,会在$.cache对象开辟一个空对象(缓存体),用于放置与目标对象有关的东西。这有点像银行开户了,UUID的值就是存折。removeData则会删掉不再需要保存数据,如果到最后,数据删清光了,它也没有任何键值对,成为空对象,jQuery就会从$.cache中删掉此对象,并从目标对象移除UUID。

代码如下:

//jQuery1.2.3
var expando = "jQuery" + (new Date()).getTime(), uuid = 0, windowData = {};
jQuery.extend({
cache: {},
data: function( elem, name, data ) {
elem = elem == window ? windowData : elem;//对window对象做特别处理
var id = elem[ expando ];
if ( !id ) //如果没有UUID则新设一个
id = elem[ expando ] = ++uuid;
//如果没有在$.cache中开户,则先开户
if ( name && !jQuery.cache[ id ] )
jQuery.cache[ id ] = {};

// 第三个参数不为undefined时,为写操作
if ( data != undefined )
jQuery.cache[ id ][ name ] = data;
//如果只有一个参数,则返回缓存对象,两个参数则返回目标数据
return name ? jQuery.cache[ id ][ name ] : id;
},

removeData: function( elem, name ) {
elem = elem == window ? windowData : elem;
var id = elem[ expando ];
if ( name ) {//移除目标数据
if ( jQuery.cache[ id ] ) {
delete jQuery.cache[ id ][ name ];
name = "";

for ( name in jQuery.cache[ id ] )
break;
//遍历缓存体,如果不为空,那name会被改写,如果没有被改写,则!name 为true,
//从而引发再次调用此方法,但这次是只传一个参数,移除缓存体,
if ( !name )
jQuery.removeData( elem );
}
} else {
//移除UUID,但IE下对元素使用delete会抛错
try {
delete elem[ expando ];
} catch(e){
if ( elem.removeAttribute )
elem.removeAttribute( expando );
}//注销账户
delete jQuery.cache[ id ];
}
}
})

jQuery在1.2.3中添加了两个同名的原型方法data与removeData,目的是方便链式操作与集化操作。并在data中添加getData, setData的自定义事件的触发逻辑。

1.3中,数据缓存系统终于独立成一个模块data.js(内部开发时的划分),并添加了两组方法,命名空间上的queue与dequeue,原型上的queue与dequeue。queue的目的很明显,就是缓存一组数据,为动画模块服务。dequeue是从一组数据中删掉一个。

代码如下:

//jQuery1.3
  jQuery.extend({
   queue: function( elem, type, data ) {
   if ( elem ){
   type = (type || "fx") + "queue";
   var q = jQuery.data( elem, type );
   if ( !q || jQuery.isArray(data) )//确保储存的是一个数组
   q = jQuery.data( elem, type, jQuery.makeArray(data) );
   else if( data )//然后往这个数据加东西
   q.push( data );
   }
   return q;
   },
   dequeue: function( elem, type ){
   var queue = jQuery.queue( elem, type ),
   fn = queue.shift();//然后删掉一个,早期它是放置动画的回调,删掉它就call一下,
   // 但没有做是否为函数的判定,估计也没有写到文档中,为内部使用
   if( !type || type === "fx" )
   fn = queue[0];
   if( fn !== undefined )
   fn.call(elem);
   }
  })

fx模块animate方法的调用示例:

代码如下:

//each是并行处理多个动画,queue是一个接一个处理多个动画
  this[ optall.queue === false ? "each" : "queue" ](function(){ /*略*/})

在元素上添加自定义属性,还会引发一个问题。如果我们对这个元素进行拷贝,就会将此属性也会复制过去,导致两个元素都有相同的UUID值,出现数据被错误操作的情况。jQuery早期的复制节点实现非常简单,如果元素的cloneNode方法不会复制事件就使用cloneNode,否则使用元素的outerHTML,或父节点的innerHTML,用clean方法解析一个新元素出来。但outerHTML与innerHTML都会显式属性写在里面,因此需要用正则把它们清除掉。


代码如下:

  //jQuery1.3.2 core.js clone方法
  var ret = this.map(function(){
   if ( !jQuery.support.noCloneEvent && !jQuery.isXMLDoc(this) ) {
   var html = this.outerHTML;
   if ( !html ) {
   var div = this.ownerDocument.createElement("div");
   div.appendChild( this.cloneNode(true) );
   html = div.innerHTML;
   }
  
   return jQuery.clean([html.replace(/ jQuery\d+="(?:\d+|null)"/g, "").replace(/^\s*/, "")])[0];
   } else
   return this.cloneNode(true);
  });

jQuery1.4发现IE如果对于object, ember, applet这三个古老的用于接入外部资源的标签可能会抛错。由于旧式IE的元素节点只是COM的包装,一旦引入资源后,它就会变成那种资源的实例,而它们会有严格的访问控制,不能像普通的JS对象那样随意添加成员。于是jQuery便一刀换,但凡是这三种标签,就不为它缓存数据。jQuery弄了一个叫noData的hash,用于检测元素节点的标签。


代码如下:

  noData: {    "embed": true,
   "object": true,
   "applet": true  },
  //代码防御
  if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) {
   return;
  }

jQuery1.4还对$.data进行改进,允许第二个参数为对象,方便储存多个数据。UUID对应的自定义属性expando 也放进命名空间之下了。queue与dequeue方法被剥离成一个新模块。
jQuery1.43带来三项改进。
首先是添加changeData自定义方法。不过这套方法没有什么销量,只是产品经理的自恋吧。
检测元素节点是否支持添加自定义属性的逻辑被独立成一个叫acceptData的方法。因为jQuery团队发现当object标签加载的flash资源,它还是可以添加自定义属性的,于是决定对这种情况网开一面。IE在加载flash时,需要对object指定一个叫classId的属性,值为clsid:D27CDB6E-AE6D-11cf-96B8-444553540000,因此检测逻辑就变得非常复杂,由于data, removeData都要用到,独立出来有效节省比特。
HTML5对人们随便添加自定义属性的行为做出回应,新增一种叫"data-*"的缓存机制。当用户设置的属性以"data-"开头,它们会被保存到元素节点的dataset对象上。这就导致人们可能用HTML5方便缓存数据,也可能用jQuery的缓存系统保存数据,那么data方法就变得有点不中用了。于是jQuery在原型上的data做了增强,当用户第一次访问此元素节点,会遍历它所有"data-"开头的自定义属性(为了照顾旧式IE,不能直接遍历dataset),把它们放到jQuery的缓存体中。那么当用户取数据时,会先从缓存系统中,没有再使用setAttribute访问"data-"自定义属性。但HTML5的缓存系统非常弱,只能保存字符串(这当然是出于循环引用的考量),于是jQuery会将它们还原为各种数据类型,如"null",, "false", "true"变成null, false, true, 符合数字格式的字符串会转换成数字,如果它是以"{"开头"}"结尾则尝试转成一个对象。


代码如下:

  //jQuery1.43 $.fn.data
  rbrace = /^(?:\{.*\}|\[.*\])$/;
  if ( data === undefined && this.length ) {
   data = jQuery.data( this[0], key );
   if ( data === undefined && this[0].nodeType === 1 ) {
   data = this[0].getAttribute( "data-" + key );
  
   if ( typeof data === "string" ) {
   try {
   data = data === "true" ? true :
   data === "false" ? false :
   data === "null" ? null :
   !jQuery.isNaN( data ) ? parseFloat( data ) :
   rbrace.test( data ) ? jQuery.parseJSON( data ) :
   data;
   } catch( e ) {}
  
   } else {
   data = undefined;
   }
   }
  }

jQuery1.5也带来三项改进。当时jQuery已经在1.42打败Prototype.js,如日中天,马太效应,用户量暴增。它的重点改为提升性能,进入fix bug阶段(用户多,相当于免费的测试员就越多,测试覆盖面就越大)。
改进expando,原来是基于时间截,现在是版本号加随机数。因此用户可能在一个页面引入多个版本的jQuery。
是否有此数据的逻辑被抽出成一个hasData方法,处理HTML5的"data-*"属性也被抽出成一个私有方法dataAttr。它们都是为了逻辑显得更清晰。dataAttr使用JSON.parse,由于这个JSON可能是JSON2.js引入的,而JSON2.js有个非常糟糕的地方,就是为一系列原生类型添加了toJSON方法,导致for in 循环判定是否为空对象出错。jQuery被逼搞了个isEmptyDataObject方法做处理。
jQuery的数据缓存系统本来就是为事件系统服务而分化出来的,到后来,它是内部众多模块的基础设施。换言之,它内部会储存许多框架用户的变量(系统数据),但一旦它公开到文档中,用户也会使用data保存他们务业中使用的数据(用户数据)。以前,用户小,变量名冲突的可能性比较少,加之jQuery为这些系统数据精挑了一些不常用的名字,__class__, __change__或加个后缀什么的,没有收到什么投诉。当jQuery成为世界级的著名框架后,用户数据名干掉系统数据名,导致事件系统或其他什么模块瘫痪就时有发生。jQuery开始对缓存体进行改造,原来就是一个对象,什么数据都往里面抛。现在它就这个缓存体内开辟一个子对象,键名为随机的jQuery.expando值,如果是系统数据就存到里面去。但events系统数据为了向前兼容起见,还是直接放到缓存体之上。至于,如何区分是系统数据,非常简单,直接在data方法添加第四个参数,真值时为系统数据。removeData时也相应提供第三个参数,用于删除系统数据。还新设了一个_data方法,专门用于操作系统数据。下面就是缓存体的结构图:
 

代码如下:

 
var cache = {
jQuery14312343254:{/*放置系统数据*/}
events: {/"放置事件名与它对应的回调列表"/}
/*这里放置用户数据*/
}

jQuery1.7对缓存体做了改进,系统变量变放置data对象中,为此判定缓存体为空也要做相应的改进,现在要跳过toJSON与data。新结构如下:


代码如下:

var cache = {
data:{/*放置用户数据*/}
/*这里放置系统数据*/
}

jQuery1.8曾添加一个叫deleteIds的数组,用于重用UUID,但昙花一现。UUID的值从1.8起不用jQuery.uuid的了,改用jQuery.guid递增生成。重大的改进在jQuery1.83后,操作数据的实现被抽出为私有方法,命名空间与原型上的方法只是一个代理,并分成两组方法,操作用户数据的data, removeData,操作系统数据的_data,_removeData。现在光是缓存系统就是一个庞大家族了。
 
说到底,数据缓存就是在目标对象与缓存体间建立一对一的关系,然后在缓存体上操作数据,复杂度都集在前者。而在一个普通JS对象进行增删改查某属性从来没有难度,用户怎么也玩不出花招。从软件设计原则上看,这也是最好的结果(吻合KISS原则与职责单一则)。

(0)

相关推荐

  • jQuery的缓存机制浅析

    前不久在研究jQuery的动画队列的时候,发现jQuery的缓存系统也很强大,尽管以前也稍微接触过,但一直都没有深入研究过.jQuery的缓存系统在外部应用的时候都比较简单,比如要将某个URL数据存到缓存中只要这么写: 复制代码 代码如下: var val = "stylechen.com";$("div").data( "url" ); // 返回undefined$("div").data( "url"

  • jQuery数据缓存功能的实现思路及简单模拟

    前言 对于jQuery的数据缓存,相信大家都不会陌生,jQuery缓存系统不仅运用于DOM元素,动画.事件等都有用到这个缓存系统.所以在平时实际应用中, 我们经常需要给元素缓存一些数据,并且这些数据往往和DOM元素紧密相关.由于DOM元素(节点)也是对象, 所以我们可以直接扩展DOM元素的属性,但是如果给DOM元素添加自定义的属性和过多的数据可能会引起内存泄漏,所以应该要尽量避免这样做. 因此更好的解决方法是使用一种低耦合的方式让DOM和缓存数据能够联系起来. 另外:对于jQuery.data和

  • jquery 缓存问题的几个解决方法

    .load方法没有设置 cache参数 默认true ,特别在IE浏览器下,一般的ajax的方法都是cache等于true的! 解决办法几个: 1.使用.ajax方法并设置 cache参数为false 复制代码 代码如下: $.ajaxSetup ({ cache: false }); $(fucntion({ $.ajaxSetup ({ cache: false }); })) 在每次执行load方法前执行,注意不能设置为全局属性 可以写一个方法每次打开页面都引用这个方法 2.修改jquer

  • jQuery ajax cache缓存问题

    在jquery里面用$.ajax 然后, 指定ajax属性的时候, 使用了:'false' . 可恶的javascript特性就在这里出现了. 在jquery里面, 大部分的时候, 属性要用''引起来. 否则容易变成变量名.比如red, 引不引都差不多.但是引起来也可以认. 所以习惯就全部引起来好了. 然而, false是个特例. 如果引起来, 导致的结果: 'false' 按true处理. 因为只有空字串才是true. 例如:在浏览器地址栏输入: javascript:alert('false

  • jQuery数据缓存用法分析

    本文分析了jQuery数据缓存用法.分享给大家供大家参考.具体如下: 在jQuery的API帮助文档中,jQuery这样描述数据缓存的作用:用于在一个元素上存取数据而避免了循环引用的风险. 一.定义缓存数据 使用$(selector).data(name,value)方法可以为jQuery对象定义缓存数据.这些缓存数据被存放在匹配的DOM元素集合中所有DOM元素中. var $link = $('a'); $link.data('linkType', 'home'); 说明:$(selector

  • 解析jquery中的ajax缓存问题

    jquery的ajax请求默认请求cache是true 也就是开启的,dataType为script和jsonp时默认为false.现在我要在浏览器里读取缓存,因为ajax请求的数据很大,请求一次就够了.但是问题来了,在FF里面,是没有ajax缓存的,也就是每次都会触发ajax请求,这点和IE不一样.所以在这里就得注意,做个判断,阻止触发ajax事件. 复制代码 代码如下: function ajax_show(apartId,roomClass,sortTile){          HX_T

  • jQuery对象数据缓存Cache原理及jQuery.data方法区别介绍

    网上有很多教你怎么使用jQuery.data(..)来实现数据缓存,但有两个用户经常使用的data([key],[value])和jQuery.data(element,[key],[value])几乎没有什么文章说清楚它们两的区别,所以我用到了,研究下分享给大家. $("").data([key],[value])与jQuery.data(element,[key],[value])的区别 这两个函数都是用来在元素上存放数据也就平时所说的数据缓存,都返回jQuery对象,当时我分别在

  • jQuery 数据缓存模块进化史详细介绍

    数据缓存系统最早应该是jQuery1.2引入的,那时它的事件系统完成照搬DE大神的addEvent.js,而addEvent在实现有个缺憾,它把事件的回调都放到EventTarget之上,这会引发循环引用,如果EventTarget是window对象,又会引发全局污染.有了数据缓存系统,除了规避这两个风险外,我们还可以有效地保存不同方法产生的中间变量,而这些变量会对另一个模块的方法有用,解耦方法间的依赖.对于jQuery来说,它的事件克隆乃至后来的列队实现都是离不开缓存系统. jQuery1.2

  • Vue路由元信息与懒加载和模块拆分详细介绍

    目录 1. 路由元信息 2. 路由懒加载和模块拆分 1. 路由元信息 描述: 元信息就是对于路由规则的额外补充信息,也就是在定义路由的时候可以配置 meta 字段. 元信息通俗点来说,就是写给程序看的注释. 语法: const router = new VueRouter({ routes: [ { path: '/admin', component: Admin, // 元信息 meta: {login:true} } ] }) 使用: 路由规则(new.js): import News fr

  • jQuery 数据缓存data(name, value)详解及实现

    作为一名程序员,一提到"缓存"你很容易联想到"客户端(浏览器缓存)"和"服务器缓存".客户端缓存是存在浏览者电脑硬盘上的,即浏览器临时文件夹,而服务器缓存是存在服务器内存中,当然在一些高级应用场合也有专门的缓存服务器,甚至有利用数据库进行缓存的实现.当然这些都不在本文的讨论范围,本文要讨论的是最流行的JavaScript框架jQuery的数据缓存实现原理,这是jQuery1.2.3版开始加入的新功能. 一. jQuery数据缓存的作用 jQuer

  • Python 模块EasyGui详细介绍

    Python 模块EasyGui详细介绍 前言: 在Windows想用Python开发一些简单的界面,所以找到了很容易上手的EasyGui库.下面就分享一下简单的使用吧. 参考的链接:官网Tutorial 接下来,我将从简单,到复杂一点点的演示如何使用这个模块.希望能给刚接触easygui的你一点帮助 :-) msgBox,ccbox,ynbox # coding:utf-8 # __author__ = 'Mark sinoberg' # __date__ = '2016/5/25' # __

  • Python标准库datetime date模块的详细介绍

    目录 前言 1.定义 1.2.常见错误 2.date类常用的函数 2.1.获取当期日期 2.2.格式化日期 2.2.1.ctime() 2.2.2.datetime.date对象 2.2.3.replace(self, year=None, month=None, day=None) 2.2.4.格式化日期 2.3.ISO标准格式日期 2.3.1.获取符合ISO标准格式的日期字符串的星期几(1~7) 2.3.2.返回日期或者时间对象的星期几(0~6) 2.3.3.根据时间戳计算日期 2.3.4.

  • python中内置库os与sys模块的详细介绍

    目录 os包 sys模块 os包 想要使用os包一样要先导入:import os os包下可以直接调用的函数 下面介绍一下os包中可以直接调用的函数: 例子: 例子: 例子: 注意:os.path.exists()参数可以传绝对路径,也可以传相对路径: 已知一个文件的路径,可以用spilt切割出这个文件名: sys模块 sys模块常用于操作当前的操作系统/环境 sys中常用的函数: 例子: 关于argv我们知道,他可以从程序外部获取参数,我们让他从终端传入参数给程序. 举一个详细的例子介绍: 可

  • JQuery入门—JQuery程序的代码风格详细介绍

    <jQuery权威指南>第1章jQuery开发入门,本章通过循续渐进的方式,先从jQuery的基础概念入手,介绍jQuery库的下载,引入简单应用方法:后部分侧重于jQuery控制DOM对象和页面CSS样式的介绍,通过一些简单的小示例,使读者对jQuery在页面中的功能应用有一个大致的了解,为下一章节进一步学习jQuery库的详细对象和方法奠定基础.本节为大家介绍jQuery程序的代码风格. 1.1.5 jQuery程序的代码风格 1."$"美元符的使用 在jQuery程序

  • Jquery 数据选择插件Pickerbox使用介绍

    该插件就是为此而生,所以就写了一个. 不过目前并不支持IE6浏览器. 希望大家能多多指教. :) pickerbox 1.0 介绍 pickerbox简称采摘盒子,是基于Jquery的插件,同时也依赖了Ztree3.2,Easyui1.26,及AsyncBox1.4.5.主要为了解决选择多条件的数据. -封装主要插件复杂的使用,关心主要的常用使用. -支持树,导航导向呈现数据. -支持跨iframe获取数据. -灵活支持记录数据. -提供重要事件回调. -兼容IE8以上浏览器. 由于多个插件集成

随机推荐