转:jQuery.data

    • 原文地址:http://www.it165.net/pro/html/201404/11922.html

      内存泄露

      首先看看什么是内存泄露,这里直接拿来Aaron中的这部分来说明什么是内存泄露,内存泄露的3种情况:

      1 循环引用

      2 Javascript闭包

      3 DOM插入顺序

      在这里我们只解释第一种情况,因为jquery的数据缓存就是解决这类的内存泄露的。一个DOM对象被一个Javascript对象引用,与此同时又引用同一个或其它的Javascript对象,这个DOM对象可能会引发内存泄漏。这个DOM对象的引用将不会在脚本停止的时候被垃圾回收器回收。要想破坏循环引用,引用DOM元素的对象或DOM对象的引用需要被赋值为null。

      含有DOM对象的循环引用将导致大部分当前主流浏览器内存泄露

      第一种:多个对象循环引用

      1.var a=new Object;
      2. 
      3.var b=new Object;
      4. 
      5.a.r=b;
      6. 
      7.b.r=a;

      第二种:循环引用自己

      1.var a=new Object;
      2. 
      3.a.r=a;

      循环引用很常见且大部分情况下是无害的,但当参与循环引用的对象中有DOM对象或者ActiveX对象时,循环引用将导致内存泄露。

      我们把例子中的任何一个new Object替换成document.getElementById或者document.createElement就会发生内存泄露了。

      在实际应用中我们要给我们的DOM添加数据,如果我们给一个DOM添加的数据太多的话,会存在循环引用的风险,例如我们添加的数据恰好引用了这个DOM元素,就会存在内存的泄露。所以jquery使用了数据缓存的机制就解决或者说避免这一问题。

      数据缓存

      $.cache 是jquery的缓存对象,这个是对象就是一个json,它的结构是这样的

      1.{ 'uid1': { // DOM节点1缓存数据,
      2.        'name1': value1,
      3.        'name2': value2
      4.    },
      5.    'uid2': { // DOM节点2缓存数据,
      6.        'name1': value1,
      7.        'name2': value2
      8.    }

      数据缓存的接口是

      $.data( element, key, value )

      $(selector).data(key,value)

      用法

      看代码之前,先看看怎么使用jquery的数据缓存。在jquery中,有两个方法可以给对象设置数据,分别是实例方法$().data()和静态方法$.data(),具体的使用过程大家看api就知道了,这里简单介绍下

      静态方法$.data()有三个参数,分别是挂在数据的元素,挂载的数据键,挂载数据的值,根据参数的不同,无非就是设置数据,取数据,具体如下

      1 $.data( elem, key, value ) 在指定元素上存储/添加任意的数据,处理了循环引用和内存泄漏问题
       2 $.data( elem, key ) 返回指定元素上name指定的值
       3 $.data( elem ) 返回全部数据
       4 $.data( elem,obj ) 在指定的元素上绑定obj

      01.var obj = {};
      02.$.data(obj , 'a' , 1);//普通对象添加数据
      03.console.log($.data(obj,'a'));//1
      04.var dom = $('body');//dom添加数据
      05.$.data(dom,'a',1)
      06.console.log($.data(dom,'a'));//1
      07.$.data(obj , {'b':2});//两个参数 绑定数据对象
      08.console.log($.data(dom,'b'));//2
      09.console.log($.data(dom));//1 2

      静态方法$().data()有两个参数,挂载的数据键,挂载数据的值

      1 $(selector).data( key, value ) 在指定元素上存储/添加任意的数据,处理了循环引用和内存泄漏问题
       2 $(selector).data( key ) 返回指定元素上name指定的值
       3 $(selector).data(obj ) 在指定的元素上绑定obj 
       4 $(selector).data() 返回全部数据

      1.$('body').data('a' , 1);//添加数据
      2.console.log($('body').data('a'));//1
      3.$('body').data({'b':2});//两个参数 绑定数据对象
      4.console.log($('body').data('b'));//2
      5.console.log($('body').data();//1 2

      思路

      回想下我们要解决什么问题:我们想在DOM上添加数据,但是不想引起内存的泄露,也就是我们不想引起循环引用,要尽量减少在DOM上挂数据。jquery的思路是这样:使用一个数据缓存对象$.cache,在需要绑定数据的DOM上扩展一个expando属性,这个属性存的是一个id,这里不会存在循环引用的情况了,之后将数据存在$.cache[id]上,当我们取DOM上的数据的时候,我们可以根据DOM上的expando找到id,进而找到存在$.cache[id]上的数据。可以看出jquery只是在DOM上扩展了一个属性expando,数据都存在了$.cache中,利用expando这个属性建立DOM和缓存对象之间的联系。无论我们添加多少的数据都会存储在缓存对象中,而不是直接挂在DOM上。这个唯一id是一个整型值,初始为0,调用data接口时自动加一,唯一id附加在以$.expando命名的属性上,$.expando是动态生成的,类似于一个时间戳,以尽可能的避免与用户变量冲突。从匹配的DOM元素上取到唯一id,在$.cache中找到唯一id对应的对象,再从对应的对象中找到key对应的值

      看例子,在源码里打断点看一下

      1.$.data($('body')[0],{'a':1});
      2.console.log($.data($('body')[0],'a'));

      转:jQuery.data

      DOM对象扩展了一个属性,这个属性存的是cache的id。

      转:jQuery.data

      这样大家就比较明显了。

      实现

      expando就是一个类似时间戳的东东,源码

      1.expando: 'jQuery' + ( jQuery.fn.jquery + Math.random() ).replace( /D/g, '' )

      就是为了生成标识的,没啥可说的。

      这是静态方法的代码的整体结构,我看到的1.10.2,变化较大,所有的方法的实现都封装成了函数,主要看 internalData( elem, name, data )这个函数,其他的大伙自己看看吧

      转:jQuery.data转:jQuery.data

      01.jQuery.extend({
      02.    cache: {},
      03. 
      04.    // The following elements throw uncatchable exceptions if you
      05.    // attempt to add expando properties to them.
      06.    noData: {
      07.        'applet': true,
      08.        'embed': true,
      09.        // Ban all objects except for <a href="http://www.it165.net/design/wfl/" target="_blank" class="keylink">Flash</a> (which handle expandos)
      10.        'object': 'clsid:D27CDB6E-AE6D-11cf-96B8-444553540000'
      11.    },
      12. 
      13.    hasData: function( elem ) {
      14.        elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
      15.        return !!elem && !isEmptyDataObject( elem );
      16.    },
      17. 
      18.    data: function( elem, name, data ) {
      19.        return internalData( elem, name, data );
      20.    },
      21. 
      22.    removeData: function( elem, name ) {
      23.        return internalRemoveData( elem, name );
      24.    },
      25. 
      26.    // For internal use only.
      27.    _data: function( elem, name, data ) {
      28.        return internalData( elem, name, data, true );
      29.    },
      30. 
      31.    _removeData: function( elem, name ) {
      32.        return internalRemoveData( elem, name, true );
      33.    },
      34. 
      35.    // A method for determining if a DOM node can handle the data expando
      36.    acceptData: function( elem ) {
      37.        // Do not set data on non-element because it will not be cleared (#8335).
      38.        if ( elem.nodeType && elem.nodeType !== 1 && elem.nodeType !== 9 ) {
      39.            return false;
      40.        }
      41. 
      42.        var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ];
      43. 
      44.        // nodes accept data unless otherwise specified; rejection can be conditional
      45.        return !noData || noData !== true && elem.getAttribute('classid') === noData;
      46.    }
      47.});
      01.function internalData( elem, name, data, pvt /* Internal Use Only */ ){
      02.    if ( !jQuery.acceptData( elem ) ) {//查看是否可以接受数据
      03.        return;
      04.    }
      05.    var ret, thisCache,
      06.        internalKey = jQuery.expando,//jQuery副本的唯一标识
      07.        // We have to handle DOM nodes and JS objects differently because IE6-7
      08.        // can't GC object references properly across the DOM-JS boundary
      09.        isNode = elem.nodeType,//判断DOM节点
      10.        // Only DOM nodes need the global jQuery cache; JS object data is
      11.        // attached directly to the object so GC can occur automatically
      12.        cache = isNode ? jQuery.cache : elem,//若是是DOM对象,则cache就是$.cache,否则为参数elem对象
      13.        // Only defining an ID for JS objects if its cache already exists allows
      14.        // the code to shortcut on the same path as a DOM node with no cache
      15.        id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey;//找id,id可能在DOM[expando]中,也可以在elem[expando]中
      16.    // Avoid doing any more work than we need to when trying to get data on an
      17.    // object that has no data at all
      18.    if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && data === undefined && typeof name === 'string' ) {
      19.        return;//参数的一些判断限制
      20.    }
      21.    if ( !id ) {//id不存在
      22.        // Only DOM nodes need a new unique ID for each element since their data
      23.        // ends up in the global cache
      24.        if ( isNode ) {//是DOM节点
      25.            id = elem[ internalKey ] = core_deletedIds.pop() || jQuery.guid++;//生成一个id
      26.        } else {//不是DOM,是一个对象
      27.            id = internalKey;//那么id就是那个expando
      28.        }
      29.    }
      30.    if ( !cache[ id ] ) {//cache中不存在数据,先弄成空的,一会在填充
      31.        // Avoid exposing jQuery metadata on plain JS objects when the object
      32.        // is serialized using JSON.stringify
      33.        cache[ id ] = isNode ? {} : { toJSON: jQuery.noop };
      34.    }
      35.    // An object can be passed to jQuery.data instead of a key/value pair; this gets
      36.    // shallow copied over onto the existing cache
      37.    if ( typeof name === 'object' || typeof name === 'function' ) {//处理第二个参数时对象或者是函数的情况
      38.        if ( pvt ) {//不太懂
      39.            cache[ id ] = jQuery.extend( cache[ id ], name );
      40.        } else {//添加到data属性上
      41.            cache[ id ].data = jQuery.extend( cache[ id ].data, name );
      42.        }
      43.    }
      44.    thisCache = cache[ id ];
      45.    // jQuery data() is stored in a separate object inside the object's internal data
      46.    // cache in order to avoid key collisions between internal data and user-defined
      47.    // data.
      48.    if ( !pvt ) {
      49.        if ( !thisCache.data ) {
      50.            thisCache.data = {};
      51.        }
      52.        thisCache = thisCache.data;
      53.    }
      54.    if ( data !== undefined ) {//第三个参数存在,就是存数据
      55.        thisCache[ jQuery.camelCase( name ) ] = data;
      56.    }
      57.    // Check for both converted-to-camel and non-converted data property names
      58.    // If a data property was specified
      59.    if ( typeof name === 'string' ) {
      60. 
      61.        // First Try to find as-is property data
      62.        ret = thisCache[ name ];//取出来待返回的那个value
      63.                //有啥用 这么麻烦
      64.        // Test for null|undefined property data
      65.        if ( ret == null ) {
      66.            // Try to find the camelCased property
      67.            ret = thisCache[ jQuery.camelCase( name ) ];
      68.        }
      69.    } else {
      70.        ret = thisCache;//就是返回存进来的那个对象或者函数
      71.    }
      72.    return ret;
      73.}

      实现起来还是比较简单的,只是有些地方jquery考虑的太周全了,我等凡人看不太透彻。

      pS:给DOM对象添加的数据是存储在了$.cache中,而给对象添加书数据直接挂在了对象的expando上面。其实给一个对象挂数据也没有什么实际的意义。

      看源码可以知道,看个例子更明显

      1.var obj = {};
      2.$.data(obj,{'a':1});
      3.console.log($.data(obj,'a'));
      4.console.log(obj);

      结果:

      转:jQuery.data

      实例方法data()其实就是调用了$.data()这个静态方法,这里就不说了。

      转:jQuery.data

      01.jQuery.fn.extend({
      02.    data: function( key, value ) {
      03.        var attrs, name,
      04.            data = null,
      05.            i = 0,
      06.            elem = this[0];
      07. 
      08.        // Special expections of .data basically thwart jQuery.access,
      09.        // so implement the relevant behavior ourselves
      10. 
      11.        // Gets all values
      12.        if ( key === undefined ) {
      13.            if ( this.length ) {
      14.                data = jQuery.data( elem );
      15. 
      16.                if ( elem.nodeType === 1 && !jQuery._data( elem, 'parsedAttrs' ) ) {
      17.                    attrs = elem.attributes;
      18.                    for ( ; i < attrs.length; i++ ) {
      19.                        name = attrs[i].name;
      20. 
      21.                        if ( name.indexOf('data-') === 0 ) {
      22.                            name = jQuery.camelCase( name.slice(5) );
      23. 
      24.                            dataAttr( elem, name, data[ name ] );
      25.                        }
      26.                    }
      27.                    jQuery._data( elem, 'parsedAttrs', true );
      28.                }
      29.            }
      30. 
      31.            return data;
      32.        }
      33. 
      34.        // Sets multiple values
      35.        if ( typeof key === 'object' ) {
      36.            return this.each(function() {
      37.                jQuery.data( this, key );
      38.            });
      39.        }
      40. 
      41.        return arguments.length > 1 ?
      42. 
      43.            // Sets one value
      44.            this.each(function() {
      45.                jQuery.data( this, key, value );//这是重点
      46.            }) :
      47. 
      48.            // Gets one value
      49.            // Try to fetch any internally stored data first
      50.            elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : null;
      51.    },

      问题

      现在我们利用源码分析一些问题

      01.var a = $('body');
      02.var b = $('body');
      03.a.data('a',1);
      04.b.data('a',2);
      05.console.log(a.data('a'));//2
      06.console.log(b.data('a'));//2
      07. 
      08.$.data(a,'b',1);
      09.$.data(b,'b',2);
      10.console.log($.data(a,'b'))//1
      11.console.log($.data(b,'b'))//2
      12. 
      13.$.data(a[0],'b',1);
      14.$.data(b[0],'b',2);
      15.console.log($.data(a[0],'b'));//2
      16.console.log($.data(b[0],'b'));//2

      看着有些晕,先看下这个

      1.var a = $('body');
      2.var b = $('body');
      3.console.log(a[0] == b[0]);//true
      4.console.log(a == b);//false
      5.console.log( $('body') == $('body'));//false

      每一次$('body')都生成一个新的对象,所以每一次都会不同,$('body')[0]都是指向同一个body对象,a 和b指向的每个新对象的地址,所以不同。

      看第一组

      1.var a = $('body');
      2.var b = $('body');
      3.a.data('a',1);
      4.b.data('a',2);
      5.console.log(a.data('a'));//2
      6.console.log(b.data('a'));//2

      在看源代码这句

      1.this.each(function() {
      2.                jQuery.data( this, key, value );
      3.            })

      调用$.data(),但是这里第一个参数为this,是原生的DOM对象,第一组中的a和b的DOM对象都是body,所以添加数据会产生覆盖现象。

      第二组和第二组是正常情况,不解释了。

      小结

      这就是我的理解,希望大家指正。以后会多分析jquery的实现过程,源码的细节太难了。

//

上一篇:Hessian学习总结(一)——Hessian入门


下一篇:dede使用方法----更换模板