jQuery源码分析之数据存储

一.引子

    大家想想,在未使用jQuery或者其他js框架前,只用原生js的时候,怎么存储数据的呢?在刚结束js的时候,我是将每个跟节点有关的属性都使用setAttribute(name,value)保存在节点上。下次取就可以直接getAttribute(name),一个节点使用这种方式保存很多数据,效率肯定不高的。在有时候要对节点保存大量的数据的时候,还有没更好的办法来存储数据呢?下面来看看jQuery是如何做的。

 

二.原理

    jQuery数据的存储原理是:

1)定义了一个对象$.cache 保存所有数据

2)对每一个存储数据的DOM节点都对应一个数字index,这个DOM节点下的所有值都存储在$.cache(index)对象中(这么看是不是觉得$.cache是一个数组? 其实js中数组和对象很像,数组也是对象,对象就是一组属性的集合)。

3)对每一个存储数据的DOM节点都生成一个唯一的index。这个index值保存在节点的expando属性中。

4)expando是什么?就是每一个jQuery框架加载的时候内部生成的一个随机序列。这个序列一个jQuery加载完只有唯一一个。

 

让图片来更清楚的描述:


jQuery源码分析之数据存储


调用$(“#test”).data(“name”)时会先找到对象属性jQuery17102199497243038011($.expando)的值(当前为1),这个值就1就是上面说的index。对象所有存储的值都是放在$.cache[“1”]对象中。
下面代码可验证:
$("#test").data("abc", "def");
var cacheIndex = document.getElementById("test")[$.expando];  //获取index
//$.expando在1.2版本里访问不到,不能
var obj = $.cache[cacheIndex];
alert(obj["abc"]);  //def 注:这是jQuery1.6 前取值方式
//注: 在jquery1.7版本中对存储值有所改变。需要obj[“data”]["abc"]才能取到。也就是说$(“#test”).data(“abc”,”def”)不是存在上述obj对象中,而是存在obj[“data”]对象中(多了一层data对象,变得更深了)。

三.源码
下面来看代码是如何做到的:
jQuery.fn.extend({
  //扩展jQuery的对象方法data
  data: function( key, value ) {
    var parts, attr, name,data = null;
    //如果key没有,执行的操作
    if ( typeof key === "undefined" ) {
      if ( this.length ) {
        /**获取对象数组第一个元素的数据缓存cache
         * (该对象包含当前元素的所有存储的数据)
         */
        data = jQuery.data( this[0] );
        //元素节点
        if ( this[0].nodeType === 1 && !jQuery._data( this[0], "parsedAttrs" ) ) {
        	attr = this[0].attributes;
        	for ( var i = 0, l = attr.length; i < l; i++ ) {
        		name = attr[i].name;
        
        		if ( name.indexOf( "data-" ) === 0 ) {
        			name = jQuery.camelCase( name.substring(5) );
        
        			dataAttr( this[0], name, data[ name ] );
        		}
        	}
        	jQuery._data( this[0], "parsedAttrs", true );
        }
      }
      return data;
      
    } else if ( typeof key === "object" ) {
      return this.each(function() {
        jQuery.data( this, key );
      });
    }
    
    parts = key.split(".");
    parts[1] = parts[1] ? "." + parts[1] : "";
    
    //.data(name)格式,取值
    if ( value === undefined ) {
      data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]);
      
      // Try to fetch any internally stored data first
      if ( data === undefined && this.length ) {
        data = jQuery.data( this[0], key );
        data = dataAttr( this[0], key, data );
      }
      
      return data === undefined && parts[1] ?
        this.data( parts[0] ) :
        data;
    		
    //.data(name, value)格式,设置值
    } else {
      return this.each(function() {
        var self = jQuery( this ),
            args = [ parts[0], value ];

        //触发事件,如果对节点设置值绑定了事件,在做操作时触发事件
        self.triggerHandler( "setData" + parts[1] + "!", args );
        jQuery.data( this, key, value );
        self.triggerHandler( "changeData" + parts[1] + "!", args );
      });
    }
  }
});
 

下面是静态方法:

 

jQuery.extend({
  /** jQuery静态方法,算是data的核心了
    * jQuery很多都是这种模式,在静态方法里定义一个核心的处理方法
    * 在对象方法中定义用户操作的接口,就是API。
    * 如事件:$("#id").click(),$("xx").hover()等等。
    * 但最终处理都是调$("#id").on() 到最后是$.event.add()方法
    */
  data: function( elem, name, data, pvt /* Internal Use Only */ ) {
    //判断当前节点能不能存储数据
    if ( !jQuery.acceptData( elem ) ) {
      return;
    }

    var privateCache, thisCache, ret,
        internalKey = jQuery.expando,
        getByName = typeof name === "string",

      /**判断元素是否是DOM对象,如果是DOM对象才使用全局cache,普通js对象,直接增加属性即可.
        * DOM对象可以直接增加属性,为什么这个非得用全局对象呢?
        * 这主要是解决IE6-7浏览器垃圾回收对于js创建的DOM对象的属性不能回收
        * 验证方法:(使用sIEve 可看到有内存泄露)
        * function A() {
        *   var a = document.createElement("div");
        *   a["b"] = function(){};
        *   document.getElementById("test").appendChild(a);
        *   a.parentNode.removeChild(a);
        * }
        * A();
        */
        isNode = elem.nodeType,
        cache = isNode ? jQuery.cache : elem,
        //获取index
        id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey,
        isEvents = name === "events";

    // Avoid doing any more work than we need to when trying to get data on an
    // object that has no data at all
    if ( (!id || !cache[id] || (!isEvents && !pvt && !cache[id].data)) && getByName && data === undefined ) {
      return;
    }
    
    if ( !id ) {
      //为每一个存储数据在全局cache中的DOM节点分配一个唯一index
      if ( isNode ) {
        elem[ internalKey ] = id = ++jQuery.uuid;
      } else {
        id = internalKey;
      }
    }

    if ( !cache[ id ] ) {
      cache[ id ] = {};
      
      // Avoids exposing jQuery metadata on plain JS objects when the object
      // is serialized using JSON.stringify
      if ( !isNode ) {
        cache[ id ].toJSON = jQuery.noop;
      }
    }

    //如果name是对象或函数,继承里面所有方法.注意:这边为什么判断pvt ?
    if ( typeof name === "object" || typeof name === "function" ) {
      if ( pvt ) {
        cache[ id ] = jQuery.extend( cache[ id ], name );
      } else {
        cache[ id ].data = jQuery.extend( cache[ id ].data, name );
      }
    }

    privateCache = thisCache = cache[ id ];

    //要知道pvt是jQuery自己内部使用的,将保存用户和jQuery内部数据的保存分开了。
    //将我们设置的name/value值
    //放在了cache[id].data对象中,而jQuery内部数据直接放在了cache[id]中
    //好处不言而喻,将用户和jQuery框架的数据分开保存,避免了名称冲突
    //1.7版本之后才区分开.上面例子已经说了
    if ( !pvt ) {
      if ( !thisCache.data ) {
        thisCache.data = {};
      }
      
      thisCache = thisCache.data;
    }
    //存储数据
    if ( data !== undefined ) {
      thisCache[ jQuery.camelCase( name ) ] = data;
    }

    // Users should not attempt to inspect the internal events object using jQuery.data,
    // it is undocumented and subject to change. But does anyone listen? No.
    if ( isEvents && !thisCache[ name ] ) {
      return privateCache.events;
    }
    
    if ( getByName ) {
      ret = thisCache[ name ];
      if ( ret == null ) {
        ret = thisCache[ jQuery.camelCase( name ) ];
      }
    } else {
      ret = thisCache;
    }
    return ret;
  }
});
 

本章结束,有不对和不准确的地方望大家指正。有疑问欢迎留言。

 

你可能感兴趣的:(jquery)