jQuery中有三种添加数据的方法,$().attr(),$().prop(),$().data()。但是前面两种是用来在元素上添加属性值的,只适合少量的数据,比如:title,class,name等。对于json这种数据量大的,就适合用data方法来添加,而data方法就是jQuery缓存机制最重要的方法。

jQuery中为什么要用缓存机制系统呢?因为DOM元素与js对象之间互相引用,在大部分浏览器下会引起内存泄漏。为了解决这个问题,jQuery就写了一个缓存机制系统。举个例子:

var div = document.getElementById("div1");

var obj = {};

div.name = obj;

obj.age = div;

以上代码,div元素引用js对象obj,obj引用了div元素,互相引用,导致内存泄漏。

$().attr(),$().prop()这两个方法,在元素上挂载js对象时就有可能出现互相引用的问题。比如:$("#div1").attr(name,obj) ,obj引用div1元素,如果挂载字符串或者数字就不会出现互相引用的问题。但是data方法不管挂载什么都不会出现这种情况。

data的原理:

当调用$("#div1").data("name",obj)时,会在元素div上添加一个自定义属性xxx,它的值是jQuery中一个累加的唯一值,这里是1,然后再jQuery的全局变量cache对象添加1这个属性,它的属性值是一个json,这个json对象中就会有name属性,属性值就是obj对象。当你调用$("body").data("age",obj)时,会在body元素中添加xxx属性,它的值是2,然后在cache对象中添加2这个属性,它的属性值也是一个json,这个json中就会有age这个属性,属性值就是obj。

从以上可以看出,cache中存储的name,age,以及它们的值,跟元素没有直接关系,所以不存在互相引用的现象,它们是通过一个自定义属性和自定义属性值(jQuery中唯一的number值,元素上挂载number不会出现互相引用的结果)进行关联的。接下来,我们先来看一下jQuery的缓存系统如何使用:

实例方法使用的方式:

$("#div1").data("name","hello");   //给元素div设置name数据值

$("#div1").data("name");    //取元素div设置的name值

$("#div1").removeData("name");    //删除元素div的name数据值

静态方法使用的方式:

$.data(document.body,"age",30);  //给元素body设置age数据值

$.data(document.body,"age");  //取元素body设置的age值

$.removeData(document.body,"age");  //删除元素body的age数据值

$.hasData(document.body,"age");  //元素body是否设置了age数据值

最后我们来看一下源码,jQuery的缓存系统,其实就是使用Date这个对象实现的:

function Data() {
  Object.defineProperty( this.cache = {}, 0, {  //此方法是操作js对象内部属性的
    get: function() {      //给cache对象添加了一个0属性,它的属性值无法被修改
      return {};
    }
  });

  //我们先来解释一下这个方法的作用,比如:var obj = {name : "hello"};obj.name = "chaojidan";这里是可以修改obj.name属性值的。但是如果我们在obj.name = "chaojidan"之前添加这一行代码Object.freeze(obj),这时obj.name还是"hello",不会改变成"chajidan"。这里的Object.defineProperty方法跟Object.freeze方法的效果是一样的。举个例子:var obj = {name : "hello"};Object.defineProperty(obj,0,{get: function() { return {};} }   ),
此方法接收三个参数,第一个参数就是我们要设置的对象obj,第二个参数是属性名,也就是我们在obj对象中添加了0这个属性,第三个参数就是0属性的属性值。因此这时obj = { name:"hello", 0:{ get:function(){ return {};} }  };,然后你再obj[0] = 123; 这时obj[0]不会改变成123,而是get方法返回的值{}(因为0属性值json对象中只有get方法,没有set方法,get方法用来获取,set方法用来设置。如果有set方法,就可以设置obj[0]的值了)。

  this.expando = jQuery.expando + Math.random();  //唯一的一个标识,就是当你在元素节点上添加数据时,会在元素节点上添加自定义属性,这个自定义属性的名字就是this.expando的值。
}

Data.uid = 1;//就是一个累加的数字,也就是cache对象这边的唯一属性名。第二个元素添加数据时,它在cache对象中的属性名就是2,而第一元素的属性名就是1,第三个元素的属性名就是3....

Data.accepts = function( owner ) {   //如果是元素节点,元素必须是Element或者Document,其他元素节点都不能添加数据到cache对象中
  return owner.nodeType ?owner.nodeType === 1 || owner.nodeType === 9 : true;
};

Data.prototype = {
  key: function( owner ) {  //分配映射,让某元素和cache对象中的属性对象一一对应
    if ( !Data.accepts( owner ) ) {   //判断此元素是否能够把数据添加到cache中,如果能够添加,就会执行后面的代码,返回一个唯一的累加的数字,就是上面的Data.uid++。如果不能添加,就直接返回0。而这个0属性是不能设置数据的,只能获取。
      return 0;
    }

    var descriptor = {},
      unlock = owner[ this.expando ];

    if ( !unlock ) {   //如果此元素节点之前没有设置这个自定义属性值,就进入if语句设置
      unlock = Data.uid++;  //分配一个唯一的number值也就是ID。

      try {   
        descriptor[ this.expando ] = { value: unlock };   //descriptor  = {this.expando :{value:Data.uid++}}
        Object.defineProperties( owner, descriptor );//此方法的意思是,对descriptor对象中的每个属性进行defineProperty操作。这个代码可以写成Object.defineProperty( owner, this.expando,{value:Data.uid++});在owner这个元素节点上添加this.expando属性名(自定义属性),它的值是value的属性值Data.uid++(也就是唯一的一个number值),并且这个属性值无法被修改,只能获得。有些浏览器不支持这个方法,所以就catch

      } catch ( e ) {
        descriptor[ this.expando ] = unlock;  //descriptor = {this.expando:ID}
        jQuery.extend( owner, descriptor );//给元素节点添加这个自定义属性this.expando,并且设置值为ID。也就是元素上会添加一个这样的属性this.expando(自定义属性) = Data.uid++(唯一的一个number值);
      }
     }

    if ( !this.cache[ unlock ] ) {   //在cache对象中设置ID的属性名,它的属性值为{}
      this.cache[ unlock ] = {};
    }

    return unlock;    //当对同一个元素添加第二个数据时,就会直接返回这个owner[ this.expando ]了,所以同一个元素的自定义属性值是一样的。
  },
  set: function( owner, data, value ) {//往cache对象中添加数据值
    var prop,
      unlock = this.key( owner ),   //先找到这个ID(1,2,3....)
        cache = this.cache[ unlock ];  //在cache中找这个属性名ID的json对象

    if ( typeof data === "string" ) {
      cache[ data ] = value;  //如果是添加字符串,就直接添加到这个json对象中

    } else {   //如果是这种写法:$.data(document.body,{"age":30,"job":"it"})
      if ( jQuery.isEmptyObject( cache ) ) {
        jQuery.extend( this.cache[ unlock ], data );
      } else {
        for ( prop in data ) {
          cache[ prop ] = data[ prop ];
        }
      }
    }
    return cache;
  },
  get: function( owner, key ) { //去cache对象中获取某个值
    var cache = this.cache[ this.key( owner ) ];

    return key === undefined ?cache : cache[ key ];  //如果不传入key就返回这个元素上添加的所有数据,如果传入key,就只返回key属性的属性值。
  },
  access: function( owner, key, value ) {  //对get和set进行整合,根据参数的个数决定是get还是set操作
    var stored;
    if ( key === undefined ||((key && typeof key === "string") && value === undefined) ) {

      stored = this.get( owner, key );

      return stored !== undefined ?stored : this.get( owner, jQuery.camelCase(key) );
    }

    this.set( owner, key, value );

    return value !== undefined ? value : key;
  },
  remove: function( owner, key ) { //删除cache对象中的值
    var i, name, camel,unlock = this.key( owner ),cache = this.cache[ unlock ];

    if ( key === undefined ) {  //如果不传入key,就把这个元素的整个数据都删除
      this.cache[ unlock ] = {};

    } else {      
      if ( jQuery.isArray( key ) ) {   //如果是数组,就要删除多个属性值,比如:$.removeData(document.body,["age","job","all-name"]),name = ["age","job","all-name","allName"],map方法请参照http://www.cnblogs.com/chaojidan/p/4142338.html。
        name = key.concat( key.map( jQuery.camelCase ) );
      } else {
        camel = jQuery.camelCase( key );   //如果传入的就是一个值,先把这值转成驼峰形式
        if ( key in cache ) {    //这个值是否在cache中
          name = [ key, camel ];  //如果在,name = [key, key的驼峰写法(如果没有驼峰写法,那么就是key)]
        } else {    //如果key不在cache中
          name = camel;     //先检查key的驼峰写法在不在cache中,如果连key的驼峰写法都不在cache中,就看name是否是用空格分开的字符串,比如:"age job",那么就用正则匹配,返回[age,job]
          name = name in cache ?[ name ] : ( name.match( core_rnotwhite ) || [] );
        }
      }

      i = name.length;
      while ( i-- ) {     //删除cache中对应的属性值
        delete cache[ name[ i ] ];
      }
    }
  },
  hasData: function( owner ) { //判断cache对象是否有此属性
    return !jQuery.isEmptyObject(
      this.cache[ owner[ this.expando ] ] || {}   //元素节点在缓存系统中是否有数据
    );
  },
  discard: function( owner ) {  //一次性删除cache对象中元素节点的所有数据
    if ( owner[ this.expando ] ) {
      delete this.cache[ owner[ this.expando ] ];
    }
  }
};

以上是Data构造方法实现的源码解析,有了这个构造方法后,我们就可以实例化Data对象,通过Data实例对象来操作jQuery缓存机制了。

实例对象Data可以调用Data原型对象中的所有方法和属性,因此只要new Data,就可以通过这个new出来的Data对象进行jQuery的缓存操作。

下一课,将讲解jQuery是如何使用new出来的Data对象进行操作的。

加油!

jquery源码解析:jQuery数据缓存机制详解1的更多相关文章

  1. jQuery 源码分析(十) 数据缓存模块 data详解

    jQuery的数据缓存模块以一种安全的方式为DOM元素附加任意类型的数据,避免了在JavaScript对象和DOM元素之间出现循环引用,以及由此而导致的内存泄漏. 数据缓存模块为DOM元素和JavaS ...

  2. jquery源码解析:jQuery数据缓存机制详解2

    上一课主要讲了jQuery中的缓存机制Data构造方法的源码解析,这一课主要讲jQuery是如何利用Data对象实现有关缓存机制的静态方法和实例方法的.我们接下来,来看这几个静态方法和实例方法的源码解 ...

  3. jQuery 源码分析(十八) ready事件详解

    ready事件是当DOM文档树加载完成后执行一个函数(不包含图片,css等),因此它的触发要早于load事件.用法: $(document).ready(fun) ;fun是一个函数,这样当DOM树加 ...

  4. jQuery 源码分析(十一) 队列模块 Queue详解

    队列是常用的数据结构之一,只允许在表的前端(队头)进行删除操作(出队),在表的后端(队尾)进行插入操作(入队).特点是先进先出,最先插入的元素最先被删除. 在jQuery内部,队列模块为动画模块提供基 ...

  5. gulp源码解析(一)—— Stream详解

    作为前端,我们常常会和 Stream 有着频繁的接触.比如使用 gulp 对项目进行构建的时候,我们会使用 gulp.src 接口将匹配到的文件转为 stream(流)的形式,再通过 .pipe() ...

  6. 10.Spark Streaming源码分析:Receiver数据接收全过程详解

    原创文章,转载请注明:转载自 听风居士博客(http://www.cnblogs.com/zhouyf/)   在上一篇中介绍了Receiver的整体架构和设计原理,本篇内容主要介绍Receiver在 ...

  7. jQuery源码04 data() : 数据缓存

    /* Implementation Summary 1. Enforce API surface and semantic compatibility with 1.9.x branch 2. Imp ...

  8. Linux源码解析-内核栈与thread_info结构详解

    1.什么是进程的内核栈? 在内核态(比如应用进程执行系统调用)时,进程运行需要自己的堆栈信息(不是原用户空间中的栈),而是使用内核空间中的栈,这个栈就是进程的内核栈 2.进程的内核栈在计算机中是如何描 ...

  9. Spring源码解析--IOC根容器Beanfactory详解

    BeanFactory和FactoryBean的联系和区别 BeanFactory是整个Spring容器的根容器,里面描述了在所有的子类或子接口当中对容器的处理原则和职责,包括生命周期的一些约定. F ...

随机推荐

  1. Spring Cloud Eureka高可用落地实战

    一.原理 如图所示,多台Server端之间相互注册(这里以两台Server为例),Client端向所有的Server端注册. 二.Server端配置 1. 添加依赖 <dependency> ...

  2. ajax传递数组及后台接收

    ajax传递的是{"items":arr},其中arr=[]; 在后台String[] items=req.getParameterValues("items" ...

  3. linux下mysql开启远程访问权限及防火墙开放3306端口(mysql开放host访问权限)

    开启mysql的远程访问权限默认mysql的用户是没有远程访问的权限的,因此当程序跟数据库不在同一台服务器上时,我们需要开启mysql的远程访问权限.主流的有两种方法,改表法和授权法.相对而言,改表法 ...

  4. JavaScript事件 DOMNodeInserted DOMNodeRemoved

    JavaScript与HTML之间的交互是通过事件实现的.事件,就是文档或浏览器窗口中发生的一些特定交互的瞬间.可以使用侦听器(或处理程序)来预订事件,以便事件发生时执行相应的代码. 13.1 事件流 ...

  5. php判断一个数组是另一个数组的子集

    需求最少的时间复杂度判断$a数组是否是$b数组的子集 // 快速的判断$a数组是否是$b数组的子集$a = array(135,138);$b = array(135,138,137); 实现方法 这 ...

  6. advance shading--光源的类型

    我们这里讨论的光源类型都有一个相同点,就是,我们考量的都是光源上的一个点,对于物体表面上一个点的影响,也就是说立体角趋近为零的情况. 这里光源分为两类,一类是方向光,假设光源在无限远处.另一类是点光源 ...

  7. Determining Whether Multitasking Is Available

    [Determining Whether Multitasking Is Available] Apps must be prepared to handle situations where mul ...

  8. 访问localhost文件下的testmysql.php文件报Not Found

    但是访问localhost:8081/index.php没有报该错误,页面显示success,并没有显示wamp的主页 出错原因:Apache和php没关联好,修改一下Apache的httpd.con ...

  9. WebClient使用与IIS7最大上传文件--升级&引导窗口&目录同步完整解决方法

    IIS7最大上传文件说明:http://www.mzwu.com/article.asp?id=2449 WebClient使用说明使用using  及时回收资源 using(var wc=new W ...

  10. 将图片流输出到界面mvc

    System.Drawing.Image _CodeImage = _Code39.GetCodeImage(OrderNo, Code39.Code39Model.Code39Normal, tru ...