• jQuery1.9.1源码分析--数据缓存Data模块


    jQuery1.9.1源码分析--数据缓存Data模块

    阅读目录

    jQuery API中Data的基本使用方法介绍

         jQuery的数据缓存模块是以一种安全的方式为DOM元素附加任意类型的数据,避免了javascript和DOM之间相互引用而导致的内存泄露问题;
    当然我们在日常使用中可以给元素设置属性,比如使用attr等方法,效果是一致的,但是使用该方法会直接暴露数据到源码html元素上,但是同时也是该缺点
    获取对于数据安全性不是很高的话,也是一个优点,因为对于开发来讲很直观,便捷,直接可以看到数据的增加或者删除.
    但是使用attr也可以列出如下坏处:

    1. 循环引用;
    2. 直接暴露数据,数据的安全性需要考虑;
    3. 增加一堆的自定义标签属性,对浏览器渲染没有很大的意义;
    4. 设置值或者获取值的时候,需要对html元素dom节点操作;

    基于上面四点的缺点,我们或许可以考虑使用数据缓存Data属性来操作;下面来介绍下该数据缓存Data;
    jquery整体结构源码如下:
    jQuery.extend({
        // 全局缓存对象
        cache: {},
    
        // 页面中每个jquery副本的唯一标识
        expando:"..",
    
        noData: {},
    
        // 是否有关联的数据
        hasData: function( elem ) {},
    
        // 设置,读取自定义数据
        data: function( elem, name, data ) {},
    
        // 移除自定义数据
        removeData: function( elem, name ) {},
    
        // 设置或读取内部数据
        _data: function( elem, name, data ) {},
    
        // 是否可以设置数据
        acceptData: function( elem ) {}
    });
    jQuery.fn.extend({
        // 设置,读取自定义数据,解析html5属性 data-
        data: function( key, value ){},
    
        // 移除自定义数据
        removeData: function( key ){}
    });
    // 解析html5属性 data-
    function dataAttr( elem, key, data ){}
    
    // 检查数据缓存对象是否为空
    function isEmptyDataObject( obj ) {}
    
    jQuery.extend({
        // 清空数据缓存对象
        cleanData: function(elems, acceptData){}
    })
    我们先来看看jquery API有关Data的相应的方法介绍;
    1. jQuery.data()
    存储任意数据到指定的元素或者返回设置的值;
    存储任意数据到指定的元素上如下1
    1. jQuery.data( element, key, value )
    该方法是:存储任意数据到指定的元素,返回设置的值。
    @param element {Element} 要存储数据的DOM对象
    @param key {string} 存储的数据名
    @param value {object} 新数据值
    jQuery.data() 方法允许我们在DOM元素上附加任意类型的数据,避免了循环引用的内存泄漏风险。如果 DOM 元素是通过
    jQuery 方法删除的或者当用户离开页面时,jQuery 同时也会移除添加在上面的数据。
    如下测试代码:
    <div></div>
    <script>
         var div = $("div")[0];
         jQuery.data(div, "test", { first: 16, last: "pizza!" });
         console.log(jQuery.data(div, "test").first); // 16
         console.log(jQuery.data(div, "test").last);  // pizza
    </script>
         返回指定元素上响应名字的数据.如下2
    2. jQuery.data( element, key )
    作用: 返回用jQuery.data(element, key, value)储存在元素上的相应名字的数据,或者元素上完整的数据存储.
    它有2种形式 如下:
    1. jQuery.data( element, key )
    @param element 要关联数据的DOM对象
    @param key {string} 存储的数据名
    这是一个底层的方法,你也可用更方便的 .data()方法, 和.data()方法一样.
    2. jQuery.data( element )
    @param element 要关联数据的DOM对象
    jQuery.data(element)时将获取一个JavaScript对象,它包含了元素上所有存储的数据。

    2. .data()---(JQuery实例上实现的方法);
    在匹配元素上存储任意相关数据 或 返回匹配的元素集合中的第一个元素的给定名称的数据存储的值。
    有2种设置值的方式;如下:
    1. .data( key, value )
    @param {string} key 一个字符串,用户存储数据的名称。
    @param value 新的数据值;它可以是除了undefined任意的Javascript数据类型。
    2. .data( obj )
    @param obj {object} 一个用于更新数据的 键/值对

    实例演示demo如下:
    我们可以在一个元素上设置不同的值,之后获取这些值:
    $("body").data("foo", 52);
    $("body").data("bar", { myType: "test", count: 40 });
    $("body").data({ baz: [ 1, 2, 3 ] });
    
    console.log($("body").data("foo")); // 52
    console.log($("body").data()); // { foo: 52, bar: { myType: "test", count: 40 }, baz: [ 1, 2, 3 ] }
    
    // 获取data中的某一个对象的值
    console.log($("body").data("bar").myType); // test
      3. .data(key);
    返回匹配的元素集合中的第一个元素的给定名称的数据存储的值。 通过.data(name, value)或HTML5 data-* 属性设置;
    比如如下代码:
    console.log( $("body").data("foo")); //undefined
    $("body").data("bar", "foobar");
    console.log( $("body").data("bar")); //foobar
    如果那个元素上没有设置任何值,那么将返回undefined。比如上面的foo;

    HTML5 data-* Attributes(HTML5 data-* 属性)
    测试代码如下:
    <div data-role="page" data-last-value="43" data-hidden="true" data-options='{"name":"John"}'></div>
          var role = $("div").data("role");
          var lastValue = $("div").data("lastValue");
          var hidden = $("div").data("hidden");
          var options = $("div").data("options").name;
          console.log(role === "page");  // true
          console.log(lastValue === 43); // true
          console.log(hidden === true);  // true
          console.log(options === "John");  // true
          该元素的data-last-value属性。 如果没有传递key参数的数据存储, jQuery将在元素的属性中搜索, 将驼峰式字符串转化为中横线字符串,
    然后在结果前面加上data-。 所以,该字符串lastValue将被转换为data-last-value。

    4. jQuery.hasData( element )
    一个用于进行检查数据的DOM元素。返回值是布尔值true或者false
    jQuery.hasData()方法提供了一种方法来确定一个元素是否有任何数据,这些数据是使用jQuery.data()设置的。如果一个元素没有关联
    的data对象,该方法返回false ;否则返回true 。

    请注意,jQuery的事件系统是使用jQuery数据 存储事件处理程序的。 因此,使用.on(), .bind(), .live(), .delegate(),
    或一个速记事件方法 绑定事件到一个元素上的时候,也会在那个元素上关联一个 data 对象。
    如下测试代码:
     <p>Results: </p>
          <script>
              var $p = jQuery("p"), p = $p[0];
    
              console.log(jQuery.hasData(p)+" "); // false
    
              $.data(p, "testing", 123);
              console.log(jQuery.hasData(p)+" "); // true
    
              $.removeData(p, "testing");
              console.log(jQuery.hasData(p)+" "); // false
    
              // 使用jQuery数据 存储事件处理程序 绑定事件到一个元素上的时候,也会在那个元素上关联一个 data 对象
              $p.on('click', function() {});
              console.log(jQuery.hasData(p)+" "); // true
    
              $p.off('click');
              console.log(jQuery.hasData(p)+" "); // false
          </script>
      5. jQuery.removeData( element [, name ] )
    删除一个先前存储的数据片段。
    @param element 要移除数据的DOM对象
    @param name 要移除的存储数据名.
    注意这是一个底层的方法,你应该用.removeData()代替,.removeData()是原型实例上的方法.
    jQuery.removeData()方法允许我们移除用jQuery.data()绑定的值。当带name参数调用的时候,jQuery.removeData()将删除那个特有的值,
    当不带任何参数的时候,所有的值将被移除。
    测试代码如下:
         <div>value1 before creation: <span></span></div>
         <div>value1 after creation: <span></span></div>
         <div>value1 after removal: <span></span></div>
         <div>value2 after removal: <span></span></div>
         <script>
             var div = $("div")[0];
             $("span:eq(0)").text("" + $("div").data("test1")); // value1 before creation: undefined
             jQuery.data(div, "test1", "VALUE-1");
             jQuery.data(div, "test2", "VALUE-2");
             $("span:eq(1)").text("" + jQuery.data(div, "test1")); // value1 after creation: VALUE-1
             jQuery.removeData(div, "test1");
             $("span:eq(2)").text("" + jQuery.data(div, "test1")); // value1 after removal: undefined
             $("span:eq(3)").text("" + jQuery.data(div, "test2")); // value2 after removal: VALUE-2
         </script>
      6. .removeData( [name ] )
    在元素上移除绑定的数据.
    @param {name} 要移除的存储数据名.
    .removeData()方法允许我们移除用.data()绑定的值。当带name参数调用的时候,.removeData()将删除那个特有的值,当不带任何参数的时候,
    .removeData()将移除所有的值。
    需要注意的是.removeData()仅会删除来自jQuery内部.data()缓存中的数据, 并且元素上任何相应的data-属性不会被删除。
    但可以使用.removeAttr()来移除data-属性。
    测试代码如下:
    <div>value1 before creation: <span></span></div>
    <div>value1 after creation: <span></span></div>
    <div>value1 after removal: <span></span></div>
    <div>value2 after removal: <span></span></div>
    <script>
             var div = $("div")[0];
             $("span:eq(0)").text("" + $("div").data("test1")); // value1 before creation: undefined
             jQuery.data(div, "test1", "VALUE-1");
             jQuery.data(div, "test2", "VALUE-2");
             $("span:eq(1)").text("" + $("div").data("test1")); // value1 after creation: VALUE-1
             $("div").removeData("test1");
             $("span:eq(2)").text("" + $("div").data("test1")); // value1 after removal: undefined
             $("span:eq(3)").text("" + $("div").data("test2")); // value2 after removal: VALUE-2
     </script>

    jQuery.acceptData(elem)源码分析

    数据缓存对象源码分析如下:
    1. jQuery.acceptData(elem)
    该方法用于判断DOM元素是否可以设置数据;相关源代码如下:
    jQuery.extend({
    
             noData: {
                 "embed": true,
                 // Ban all objects except for Flash (which handle expandos)
                 "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",
                 "applet": true
             },
    
             // 是否可以设置数据
             acceptData: function( elem ) {
                 // Do not set data on non-element because it will not be cleared (#8335).
                 if ( elem.nodeType && elem.nodeType !== 1 && elem.nodeType !== 9 ) {
                     return false;
                 }
    
                 var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ];
    
                 // nodes accept data unless otherwise specified; rejection can be conditional
                 return !noData || noData !== true && elem.getAttribute("classid") === noData;
             }
         });
    1. 首先判断该元素是否是节点,且判断是不是元素节点,和文档(根节点)节点,如果都不是,则直接返回false;
    2. jQuery.noData中存放了不支持扩展属性的embed,object和applet, 该三个元素是不支持设置数据的;但是object元素,还需要检查其属性
    classid值来判断是不是Flash, 如果是Flash的话, 就可以支持设置数据的.

    jQuery.data(elem, name, data)源码分析

    源码如下:

    jQuery.extend({
            data: function( elem, name, data ) {
                return internalData( elem, name, data );
            }
    })

    具体可以看 internalData 方法的源码分析

    internalRemoveData方法源码分析

    jQuery.extend({
    removeData: function( elem, name ) {
    return internalRemoveData( elem, name );
    }
    });
    该方法通过移除使用jQuery.data()设置的数据,该方法的功能也是取决于参数的个数和类型;目前共有3种用法:
    1. jQuery.removeData(elem)
    如果没有传入参数name的话,则移除DOM关联的所有数据;
    2. jQuery.removeData(elem,name)
    如果传入了参数name的话,则移除DOM元素关联的指定name属性的数据;
    3. jQuery.removeData(elem,list)
    第二个参数还可以是数据名数组或者使用空格分割的多个数据名,用于一次性移除掉;
    该方法执行三个步骤如下:
    a. 通过关联id找到对应的数据缓存对象;
    b. 如果传入参数name,则从数据缓存对象中移除一个或者多个数据.
    c. 如果数据缓存中没有数据,则销毁这个对象.
    1. 删除自定义数据对象的cache[id].data;
    2. 删除数据缓存对象cache[id];
    3. 删除dom元素扩展的jQuery.expando属性.

    源码如下:

    function internalRemoveData( elem, name, pvt ) {
            if ( !jQuery.acceptData( elem ) ) {
                return;
            }
    
            var i, l, thisCache,
                isNode = elem.nodeType,
    
                // See jQuery.data for more information
                cache = isNode ? jQuery.cache : elem,
                id = isNode ? elem[ jQuery.expando ] : jQuery.expando;
    
            // If there is already no cache entry for this object, there is no
            // purpose in continuing
            if ( !cache[ id ] ) {
                return;
            }
    
            if ( name ) {
    
                thisCache = pvt ? cache[ id ] : cache[ id ].data;
    
                if ( thisCache ) {
    
                    // Support array or space separated string names for data keys
                    if ( !jQuery.isArray( name ) ) {
    
                        // try the string as a key before any manipulation
                        if ( name in thisCache ) {
                            name = [ name ];
                        } else {
    
                            // split the camel cased version by spaces unless a key with the spaces exists
                            name = jQuery.camelCase( name );
                            if ( name in thisCache ) {
                                name = [ name ];
                            } else {
                                name = name.split(" ");
                            }
                        }
                    } else {
                        // If "name" is an array of keys...
                        // When data is initially created, via ("key", "val") signature,
                        // keys will be converted to camelCase.
                        // Since there is no way to tell _how_ a key was added, remove
                        // both plain key and camelCase key. #12786
                        // This will only penalize the array argument path.
                        name = name.concat( jQuery.map( name, jQuery.camelCase ) );
                    }
    
                    for ( i = 0, l = name.length; i < l; i++ ) {
                        delete thisCache[ name[i] ];
                    }
    
                    // If there is no data left in the cache, we want to continue
                    // and let the cache object itself get destroyed
                    if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) {
                        return;
                    }
                }
            }
    
            // See jQuery.data for more information
            if ( !pvt ) {
                delete cache[ id ].data;
    
                // Don't destroy the parent cache unless the internal data object
                // had been the only thing left in it
                if ( !isEmptyDataObject( cache[ id ] ) ) {
                    return;
                }
            }
    
            // Destroy the cache
            if ( isNode ) {
                jQuery.cleanData( [ elem ], true );
    
            // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080)
            } else if ( jQuery.support.deleteExpando || cache != cache.window ) {
                delete cache[ id ];
    
            // When all else fails, null
            } else {
                cache[ id ] = null;
            }
         }
    该方法有三个参数
    1. elem: 待移除的DOM元素或javascript对象;
    2. name: 待移除的数据名,可以是单个数据名,数据名数组,也可以是使用空格分开的多个数据名;
    3. pvt: 指定移除的数据是内部数据还是自定义的数据,如果为true的话,说明是内部数据,否则的话是自定义数据;
    源码分析如下:
    a. 如果参数不支持设置属性的话,则直接返回;如下代码:
    if ( !jQuery.acceptData( elem ) ) {
    return;
    }
    b. 定义局部变量;源码如下:
    var i, l, thisCache,
    isNode = elem.nodeType,
    // See jQuery.data for more information
    cache = isNode ? jQuery.cache : elem,
    id = isNode ? elem[ jQuery.expando ] : jQuery.expando;

    thisCache指向DOM元素或javascript关联的数据缓存对象,如果参数pvt为true的话,则指向了内部的缓存对象,否则的话,指向了自定义的
    数据缓存对象.
    cache 指向存储数据对象;
    isNode 节点的类型;
    取出关联id,对于DOM元素而言,关联id是elem[ jQuery.expando ],对于javascript对象的话,则是jQuery.expando
    c. 如果数据缓存对象关联的id不存在的话,则直接返回;如下源码:
    if ( !cache[ id ] ) {
    return;
    }
    d. 如果传入了参数name,则移除一个或者多个数据,源码如下:
    if ( name ) {
    
           thisCache = pvt ? cache[ id ] : cache[ id ].data;
    
           if ( thisCache ) {
    
               // Support array or space separated string names for data keys
               if ( !jQuery.isArray( name ) ) {
    
                   // try the string as a key before any manipulation
                   if ( name in thisCache ) {
                       name = [ name ];
                   } else {
    
                       // split the camel cased version by spaces unless a key with the spaces exists
                       name = jQuery.camelCase( name );
                       if ( name in thisCache ) {
                           name = [ name ];
                       } else {
                           name = name.split(" ");
                       }
                   }
               } else {
                   // If "name" is an array of keys...
                   // When data is initially created, via ("key", "val") signature,
                   // keys will be converted to camelCase.
                   // Since there is no way to tell _how_ a key was added, remove
                   // both plain key and camelCase key. #12786
                   // This will only penalize the array argument path.
                   name = name.concat( jQuery.map( name, jQuery.camelCase ) );
               }
    
               for ( i = 0, l = name.length; i < l; i++ ) {
                   delete thisCache[ name[i] ];
               }
    
               // If there is no data left in the cache, we want to continue
               // and let the cache object itself get destroyed
               if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) {
                   return;
               }
           }
       }
    如上代码: thisCache = pvt ? cache[ id ] : cache[ id ].data;
    如果pvt为true的话,表示需要移除的是内部数据,则变量thisCache指向与内部数据缓存对象cache[id]; 如果pvt为false的话,
    表示需要移除的时自定义数据,则变量thisCache指向了自定义数据 cache[id].data;
    如果数据存在的话,则移除数据,如下if判断是否存在;
    if ( thisCache ) {
    }
    如果参数name不是数组的话,源码如下:
    if ( !jQuery.isArray( name ) ) {}
    如果参数name在数据缓存对象thisCache对象存在的话,则封装为数组形式,如下代码:
    if ( name in thisCache ) {
    name = [ name ];
    }
    否则把参数name转换为驼峰形式,如果驼峰形式name在数据缓存对象thisCache中存在的话,则封装为[驼峰name],否则使用空格分隔
    参数name,最后得到含有多个数据名的数组;源码如下:
    // split the camel cased version by spaces unless a key with the spaces exists
       name = jQuery.camelCase( name );
       if ( name in thisCache ) {
           name = [ name ];
       } else {
           name = name.split(" ");
       }
    如果参数name是数组的话,合并name属性成为数组形式;
    name = name.concat( jQuery.map( name, jQuery.camelCase ) );

    接着遍历参数中的name的数据名,使用运算符delete逐个从数据缓存对象this.cache中删除掉.源码如下:
    for ( i = 0, l = name.length; i < l; i++ ) {
    delete thisCache[ name[i] ];
    }
    如果thisCache对象中仍有数据的话,则直接返回;如果参数pvt为true的话,则需要调用isEmptyDataObject判断thisCache对象中是否还有
    数据,否则调用 jQuery.isEmptyObject 检查数据缓存对象this.cache 是否为空对象;
    如下代码:
    // If there is no data left in the cache, we want to continue
    // and let the cache object itself get destroyed
    if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) {
    return;
    }
    isEmptyDataObject方法的源码如下:
    // checks a cache object for emptiness
       function isEmptyDataObject( obj ) {
           var name;
           for ( name in obj ) {
    
               // if the public data object is empty, the private is still empty
               if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) {
                   continue;
               }
               if ( name !== "toJSON" ) {
                   return false;
               }
           }
    
           return true;
       }
       isEmptyObject 的源码如下:
       isEmptyObject: function( obj ) {
           var name;
           for ( name in obj ) {
               return false;
           }
           return true;
       }
    e. 删除自定义数据缓存对象cache[id].data
    如果参数pvt不是为true的话,则需要删除自定义的数据缓存对象cache[id].data; 且如果cache[id].data为空对象的话,也需要
    通过delete删除掉;如下源码:
    // See jQuery.data for more information
    if ( !pvt ) {
    delete cache[ id ].data;

    // Don't destroy the parent cache unless the internal data object
    // had been the only thing left in it
    if ( !isEmptyDataObject( cache[ id ] ) ) {
    return;
    }
    }
    f 删除数据缓存对象 cache[id]
    如果jQuery.support.deleteExpando为true的话,则支持删除DOM元素上的扩展属性,源码如下:
    else if ( jQuery.support.deleteExpando || cache != cache.window ) {
    delete cache[ id ];
    // When all else fails, null
    }
    如果为false的话,但是变量cache不是window对象的话,同样执行 delete cache[ id ];
    如果jQuery.support.deleteExpando 为false的话,并且变量cache是window对象的话,则执行:cache[ id ] = null;
    这是因为不支持删除DOM元素上扩展属性的浏览器,也不支持删除window对象的扩展属性,会抛出异常 对象不支持此操作;

    g 删除DOM元素上的扩展属性jQuery.expando属性;如下源码:
    if ( isNode ) {
    jQuery.cleanData( [ elem ], true );

    // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080)
    }

    internalData方法的源码分析

    function internalData( elem, name, data, pvt /* Internal Use Only */ ){
            if ( !jQuery.acceptData( elem ) ) {
                return;
            }
            var thisCache, ret,
                internalKey = jQuery.expando,
                getByName = typeof name === "string",
    
                // We have to handle DOM nodes and JS objects differently because IE6-7
                // can't GC object references properly across the DOM-JS boundary
                isNode = elem.nodeType,
    
                // Only DOM nodes need the global jQuery cache; JS object data is
                // attached directly to the object so GC can occur automatically
                cache = isNode ? jQuery.cache : elem,
    
                // Only defining an ID for JS objects if its cache already exists allows
                // the code to shortcut on the same path as a DOM node with no cache
                id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey;
    
            // 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] || (!pvt && !cache[id].data)) && getByName && data === undefined ) {
                return;
            }
    
            if ( !id ) {
                // Only DOM nodes need a new unique ID for each element since their data
                // ends up in the global cache
                if ( isNode ) {
                    elem[ internalKey ] = id = core_deletedIds.pop() || jQuery.guid++;
                } 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;
                }
            }
    
            // An object can be passed to jQuery.data instead of a key/value pair; this gets
            // shallow copied over onto the existing cache
            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 );
                }
            }
    
            thisCache = cache[ id ];
    
            // jQuery data() is stored in a separate object inside the object's internal data
            // cache in order to avoid key collisions between internal data and user-defined
            // data.
            if ( !pvt ) {
                if ( !thisCache.data ) {
                    thisCache.data = {};
                }
    
                thisCache = thisCache.data;
            }
    
            if ( data !== undefined ) {
                thisCache[ jQuery.camelCase( name ) ] = data;
            }
    
            // Check for both converted-to-camel and non-converted data property names
            // If a data property was specified
            if ( getByName ) {
    
                // First Try to find as-is property data
                ret = thisCache[ name ];
    
                // Test for null|undefined property data
                if ( ret == null ) {
    
                    // Try to find the camelCased property
                    ret = thisCache[ jQuery.camelCase( name ) ];
                }
            } else {
                ret = thisCache;
            }
    
            return ret;
         }
    internalData函数有四个参数,含义分别如下:
    @param elem 表示与数据关联的DOM元素.
    @param name 表示需要设置或读取的数据名.
    @param data 表示需要设置的数据值,任意类型的数据.
    @param pvt 表示设置的是内部数据还是自定义数据,如果为true的话,说明是内部数据,否则的话,是自定义数据.
    internalData函数源码分析如下:
    if ( !jQuery.acceptData( elem ) ) {
    return;
    }
    1. 判断elem是否支持设置数据,如果不支持设置数据的话,直接返回;
    2. 定义局部变量;源代码如下:
    var  thisCache, ret,
            internalKey = jQuery.expando,
            getByName = typeof name === "string",
    
            // We have to handle DOM nodes and JS objects differently because IE6-7
            // can't GC object references properly across the DOM-JS boundary
            isNode = elem.nodeType,
    
            // Only DOM nodes need the global jQuery cache; JS object data is
            // attached directly to the object so GC can occur automatically
            cache = isNode ? jQuery.cache : elem,
    
            // Only defining an ID for JS objects if its cache already exists allows
            // the code to shortcut on the same path as a DOM node with no cache
            id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey;
    
    
    各变量的含义如下:
    thisCache: 指向数据缓存对象;如果pvt为true,则指向内部数据缓存对象,否则的话,指向与自定义数据缓存对象;
    jQuery.expando: 是页面中每个jQuery副本的唯一标识; 它的值为 jQuery + 版本号 + 随机数; 然后去掉非数字字符;源代码如下:
    jQuery.extend({
    cache: {},
    // Unique for each copy of jQuery on the page
    // Non-digits removed to match rinlinejQuery
    expando: "jQuery" + ( core_version + Math.random() ).replace( /D/g, "" )
    });
    我们页面包含jquery1.9.1的库下,在chrome控制台运行下可以看到如下:
    jQuery.expando
    打印:"jQuery19106895217581005935"
    isNode: 表示参数elem是否是DOM元素;
    cache: 如果是dom元素的话,则把数据对象存储在cache对象上,为了防止javascript和DOM的循环引用,导致不能垃圾回收,因此判断是不是dom
    元素,如果是的话,存储在该对象上,如果不是的话,如果是js对象的话,直接存储在javascript对象,垃圾回收机制会自动回收js对象的.
    id: 尝试关联id,如果是DOM元素对象的话,关联id是 elem[ jQuery.expando ];否则的话,elem[ internalKey ] && internalKey;

    3. if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && getByName && data === undefined ) {
    return;
    }
    尝试没有任何数据的对象上读取数据的话,直接返回;
    (!id || !cache[id] || (!pvt && !cache[id].data) 的代码含义:
    !id的含义是: 如果没有关联id的话,说明没有数据.
    !cache[id]的含义是: 如果缓存对象中也没有关联id的话,说明没有数据.
    !cache[id].data的含义是: 如果读取的自定义数据的话,没有cache[id].data,也说明么有数据;

    getByName && data === undefined的含义是:
    如果name是字符串的话,且data是undefined,说明是在读取数据;

    4. 如果关联id不存在的话,则给分配一个关联id;代码如下:
    var core_deletedIds = [];
    if ( !id ) {
    // Only DOM nodes need a new unique ID for each element since their data
    // ends up in the global cache
    if ( isNode ) {
    elem[ internalKey ] = id = core_deletedIds.pop() || jQuery.guid++;
    } else {
    id = internalKey;
    }
    }
    上面代码的含义是: 如果没有关联id,如果是DOM元素节点的话,jQuery.guid默认为1;附加到元素上;否则的话,是javascript对象,则直接
    jQuery.expando赋值给id;
    比如我们来测试下关联id;测试代码如下:
    var body = document.body;
       var $body = $(body);
    
       // 先移除可以存在的缓存数据
       $body.removeData();
    
       // 设置自定义数据
       $body.data('public-data',1);
    
       // 设置自定义数据
       $.data(body,'public-data-2',2);
    
       // 设置内部数据
       $.data(body,'private-data',3,true);
    
       // 打印关联的id
       console.log('关联id:',$('body')[0][$.expando]); // 关联id: 1
    5. 如果数据缓存对象不存在的话,则初始化空对象 {}; 代码如下:
    var noop: function() {};
       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;
            }
       }
    如果不是DOM元素节点的话,是javascript对象的话,则这样调用 cache[ id ].toJSON = jQuery.noop;
    6. 如果name是对象或者是函数的话,则批量设置数据;
    代码如下:
    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 );
    }
    }
    如上代码;如果是对象或者是函数的话,如果pvt为true的话,则把参数name属性合并到已有的数据缓存对象中,即批量设置数据;对于内部数据,
    把参数name属性合并到cache[ id ]中,对于自定义数据的话,把参数name属性合并到cache[ id ].data中.
    7. 如果参数data不是undefined的话,则设置单个数据.如下代码:
    var rmsPrefix = /^-ms-/,
            rdashAlpha = /-([da-z])/gi;
    
        var camelCase: function( string ) {
            return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
        };
        var fcamelCase = function( all, letter ) {
            return letter.toUpperCase();
        }
    
        thisCache = cache[ id ];
        // jQuery data() is stored in a separate object inside the object's internal data
        // cache in order to avoid key collisions between internal data and user-defined
        // data.
        if ( !pvt ) {
            if ( !thisCache.data ) {
                thisCache.data = {};
            }
            thisCache = thisCache.data;
        }
        if ( data !== undefined ) {
            thisCache[ jQuery.camelCase( name ) ] = data;
        }
        如上代码,如果data不是undefined的话,则把data设置到属性name上,且统一把name转换成驼峰式,
    8. 如果参数name是字符串的话,则读取单个数据.代码如下:
    var getByName = typeof name === "string";
    if ( getByName ) {
    // First Try to find as-is property data
    ret = thisCache[ name ];

    // Test for null|undefined property data
    if ( ret == null ) {

    // Try to find the camelCased property
    ret = thisCache[ jQuery.camelCase( name ) ];
    }
    } else {
    ret = thisCache;
    }
    return ret;
    如果参数name是字符串的话,data是undefined的话,则读取单个数据.首先判断name是否有对应的数据,如果等于null,没有数据的话,
    则把name转换为驼峰式再次读取一下,如果未传入name和data的话,则进else返回语句内,如果参数pvt为true,则返回内部数据缓存对象
    jQuery.cache[id],否则的话,返回自定义的数据缓存对象 jQuery.cache[id].data;
    
    
    

    jQuery.fn.extend({data: function( key, value ) {}})源码分析

    jQuery.fn.extend({
    data: function( key, value ) {}
    });
    在原型上定义该方法的作用是:为元素设置或获取自定义数据;可以解析html5属性 data-,该方法的功能取决于参数的个数和类型,
    目前共有四种方法:
    1. .data(key,value)
    如果传递的参数是key和value,则是为每个匹配元素设置任意类型的数据.
    代码如下演示:
    2. .data(key)
    如果只传入参数key,则返回第一个匹配元素的指定名称的数据.
    对于上面1,2点;可以看下面代码演示如下:
    var $div = $("div");
    $div.data("name","111");
    console.log($div.data("name")); // 111

    3. .data()
    如果未传入任何参数的话,则返回第一个匹配元素关联的自定义数据缓存对象,包含html5属性data-中的数据.
    4. .data(obj)
    如果传入的是含有的键值对的对象的话,则为每个匹配的元素批量设置数据.
    源码如下:
    jQuery.fn.extend({
            data: function( key, value ) {
                var attrs, name,
                    elem = this[0],
                    i = 0,
                    data = null;
    
                // Gets all values
                if ( key === undefined ) {
                    if ( this.length ) {
                        data = jQuery.data( elem );
    
                        if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) {
                            attrs = elem.attributes;
                            for ( ; i < attrs.length; i++ ) {
                                name = attrs[i].name;
    
                                if ( !name.indexOf( "data-" ) ) {
                                    name = jQuery.camelCase( name.slice(5) );
    
                                    dataAttr( elem, name, data[ name ] );
                                }
                            }
                            jQuery._data( elem, "parsedAttrs", true );
                        }
                    }
    
                    return data;
                }
    
                // Sets multiple values
                if ( typeof key === "object" ) {
                    return this.each(function() {
                        jQuery.data( this, key );
                    });
                }
    
                return jQuery.access( this, function( value ) {
    
                    if ( value === undefined ) {
                        // Try to fetch any internally stored data first
                        return elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : null;
                    }
    
                    this.each(function() {
                        jQuery.data( this, key, value );
                    });
                }, null, value, arguments.length > 1, null, true );
            }
         });
    上面的源码执行4个步骤如下:
    1. 如果未传入参数的话,则返回第一个匹配元素关联的自定义数据对象.
    2. 如果参数key是对象的话,则为匹配的元素批量设置数据.
    3. 如果只传入参数key,则返回第一个匹配元素的指定名称的数据.
    4. 如果传入的参数是key和value的话,则为每个匹配的元素设置数据.

    该方法接收2个参数;
    参数key: 表示需要设置或者需要获取的键名, 或者是一个对象;
    参数value: 表示需要设置的数据值, 可以是任意类型.

    代码分析如下:
    1. 设置局部变量如下:
    var attrs,
    name,
    elem = this[0],
    i = 0,
    data = null;
    attrs的含义是: 保存元素的所有属性;
    name的含义是: 保存元素的属性名
    elem的含义是: 获取第一个元素
    2. 代码如下:
    if ( key === undefined ) {
           if ( this.length ) {
               data = jQuery.data( elem );
    
               if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) {
                   attrs = elem.attributes;
                   for ( ; i < attrs.length; i++ ) {
                       name = attrs[i].name;
    
                       if ( !name.indexOf( "data-" ) ) {
                           name = jQuery.camelCase( name.slice(5) );
    
                           dataAttr( elem, name, data[ name ] );
                       }
                   }
                   jQuery._data( elem, "parsedAttrs", true );
               }
           }
    
           return data;
       }

    如果参数key===undefined的话,且有该匹配的元素的话,则获取匹配的第一个元素关联的自定义数据缓存对象,并返回;代码如上面的
    data = jQuery.data( elem );
    如果该元素是元素节点的话,则获取该元素的所有自定义属性,进行for循环遍历,如果!name.indexOf( "data-" )为true的话,说明是
    data- 开头的自定义数据,则截取以data- 开头的后面的name属性名; 然后调用 dataAttr( elem, name, data[ name ] );该方法;
    解析含有data- 含有的数据,并把解析结果放入关联的自定义数据缓存对象中,解析完成后 该elem设置属性 parsedAttrs为true;代码如下:
    jQuery._data( elem, "parsedAttrs", true ); 通过该代码来过滤已经解析过的数据;

    函数dataAttr( elem, key, data )用于解析html5属性中的data- 中含有的数据,并把解析结果放入dom元素关联的自定义数据对象的缓存
    当中去;
    源码如下:
    var rmultiDash = /([A-Z])/g;
       var rbrace = /(?:{[sS]*}|[[sS]*])$/,
       function dataAttr( elem, key, data ) {
           // If nothing was found internally, try to fetch any
           // data from the HTML5 data-* attribute
           if ( data === undefined && elem.nodeType === 1 ) {
    
               var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
    
               data = elem.getAttribute( name );
    
               if ( typeof data === "string" ) {
                   try {
                       data = data === "true" ? true :
                           data === "false" ? false :
                           data === "null" ? null :
                           // Only convert to a number if it doesn't change the string
                           +data + "" === data ? +data :
                           rbrace.test( data ) ? jQuery.parseJSON( data ) :
                               data;
                   } catch( e ) {}
    
                   // Make sure we set the data so it isn't changed later
                   jQuery.data( elem, key, data );
    
               } else {
                   data = undefined;
               }
           }
    
           return data;
       }
       该dataAttr方法有三个参数,代码含义如下:
    elem: 表示待解析的html5中的属性data-的DOM元素;
    key: 表示待解析的数据名;不包含data-;
    data: 表示从DOM元素关联的自定义数据缓存对象中取到的数据;

    如果参数data为undefined的话,且是DOM元素节点的话,尝试从html5中含有data-中去解析数据,比如如下代码:
    var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
    增加前缀data- 并把可能的驼峰形式参数name转换为连字字符,然后调用方法data = elem.getAttribute( name );获取该属性值;
    如果data有返回值的话,如果是字符串的话,则尝试把字符串转换为javascript对象; 否则的话,直接给data赋值undefined,最后返回
    该data属性;
    比如测试data-的代码如下:
    测试代码如下:
    <div data-role="page" data-last-value="43" data-hidden="true" data-options='{"name":"John"}'></div>
    var role = $("div").data("role");
    var lastValue = $("div").data("lastValue");
    var hidden = $("div").data("hidden");
    var options = $("div").data("options").name;
    console.log(role === "page"); // true
    console.log(lastValue === 43); // true
    console.log(hidden === true); // true
    console.log(options === "John"); // true
    该元素的data-last-value属性。 如果没有传递key参数的数据存储, jQuery将在元素的属性中搜索, 将驼峰式字符串转化为中横线字符串,
    然后在结果前面加上data-。 所以,该字符串lastValue将被转换为data-last-value。

    3. 参数key是对象的话;代码如下:
    if ( typeof key === "object" ) {
    return this.each(function() {
    jQuery.data( this, key );
    });
    }
    遍历元素的集合,则为每个匹配的dom元素设置属性值,即调用jQuery.data()这个方法;代码如下:
    jQuery.data( this, key );

    4. 只传入参数key的情况下;即返回匹配第一个元素的的指定名称的数据;源码如下:
    return jQuery.access( this, function( value ) {
    if ( value === undefined ) {
    // Try to fetch any internally stored data first
    return elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : null;
    }
    }, null, value, arguments.length > 1, null, true );

    5. 传入参数为key和value的情况下;则为每个匹配的元素设置任意类型的数据,源码如下:
    this.each(function() {
    jQuery.data( this, key, value );
    });

    jQuery.extend({removeData: function( elem, name ) {}})源码分析

     源码如下:

    jQuery.fn.extend({
            removeData: function(key) {
               return this.each(function() {
                   jQuery.removeData( this, key );
               });
            }
       });
       该方法用于移除匹配元素的自定义数据,该方法是通过jQuery.removeData方法实现的;具体可以看上面的介绍;

    jQuery.cleanData(elems)源码分析

    该方法用于移除多个DOM元素关联的全部数据和事件,仅仅在jQuery内部使用,当移除DOM元素的时候,必须确保关联的数据和事件也被移除掉,
    以避免内存泄露.
    该方法执行3个关键步骤:
    1. 移除DOM元素上绑定的所有类型的事件.
    2. 移除DOM元素扩展的jQuery.expando属性.
    3. 删除DOM元素关联的数据缓存对象jQuery.cache[id]
    源码如下:
    cleanData: function( elems, /* internal */ acceptData ) {
            var elem, type, id, data,
                i = 0,
                internalKey = jQuery.expando,
                cache = jQuery.cache,
                deleteExpando = jQuery.support.deleteExpando,
                special = jQuery.event.special;
    
            for ( ; (elem = elems[i]) != null; i++ ) {
    
                if ( acceptData || jQuery.acceptData( elem ) ) {
    
                    id = elem[ internalKey ];
                    data = id && cache[ id ];
    
                    if ( data ) {
                        if ( data.events ) {
                            for ( type in data.events ) {
                                if ( special[ type ] ) {
                                    jQuery.event.remove( elem, type );
    
                                // This is a shortcut to avoid jQuery.event.remove's overhead
                                } else {
                                    jQuery.removeEvent( elem, type, data.handle );
                                }
                            }
                        }
    
                        // Remove cache only if it was not already removed by jQuery.event.remove
                        if ( cache[ id ] ) {
    
                            delete cache[ id ];
    
                            // IE does not allow us to delete expando properties from nodes,
                            // nor does it have a removeAttribute function on Document nodes;
                            // we must handle all of these cases
                            if ( deleteExpando ) {
                                delete elem[ internalKey ];
    
                            } else if ( typeof elem.removeAttribute !== core_strundefined ) {
                                elem.removeAttribute( internalKey );
    
                            } else {
                                elem[ internalKey ] = null;
                            }
    
                            core_deletedIds.push( id );
                        }
                    }
                }
            }
         }
    1. 首先检查元素elem是否支持设置数据;代码如下:
    if ( acceptData || jQuery.acceptData( elem ) ) {}
    先从DOM元素上取出关联id,然后通过id找到对应的数据缓存对象;比如如下代码:
    id = elem[ internalKey ];
    data = id && cache[ id ];

    2. 移除DOM元素上绑定的所有类型的事件.
    如果DOM元素上的事件缓存对象存在的话,并且含有属性events,说明在该元素上绑定过事件,则需要移除该元素上绑定的所有类型的事件;代码如下:
    if ( data ) {
    if ( data.events ) {
    for ( type in data.events ) {
    if ( special[ type ] ) {
    jQuery.event.remove( elem, type );

    // This is a shortcut to avoid jQuery.event.remove's overhead
    } else {
    jQuery.removeEvent( elem, type, data.handle );
    }
    }
    }
    }
    data.events 是该DOM元素的事件缓存对象;存储了该元素的所有事件;
    data.handle 是该DOM元素的监听函数;

    3. 移除DOM元素上的扩展属性jQuery.expando属性;
    源码如下:
    if ( deleteExpando ) {
    delete elem[ internalKey ];

    } else if ( typeof elem.removeAttribute !== core_strundefined ) {
    elem.removeAttribute( internalKey );

    } else {
    elem[ internalKey ] = null;
    }
    如果 jQuery.support.deleteExpando 为true的话,则支持删除DOM元素的扩展属性,则执行 delete elem[ internalKey ];
    否则看元素的类型 typeof elem.removeAttribute !== core_strundefined 是否等于core_strundefined 如果不等于的话,
    则删除属性 elem.removeAttribute( internalKey );删除DOM元素的 jQuery.expando;

    4. 删除元素的数据缓存对象 jQuery.cache[id]
    源码如下:
    if ( cache[ id ] ) {
    delete cache[ id ];
    } 

    jQuery.hasData(elem)源码分析

     该方法用于判断一个DOM元素或者javascript对象是否有与之关联的数据,如果没有的话,则返回false;否则的话,返回true.

    源码如下:
    hasData: function( elem ) {
    elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
    return !!elem && !isEmptyDataObject( elem );
    }
    对于DOM元素,需要从全局缓存对象jQuery.cache中通过关联id获取,对于javascript,则需要通过elem[ jQuery.expando ]获取;
    如果数据对象存在的话,并且含有数据的话,则返回true,否则的话 返回false;
  • 相关阅读:
    ASPNETDB 数据库关系图、表和视图 基本表和独立表(转载)
    Android工作学习笔记之图片自适应imageview属性android:scaleType
    Android 为不同的语言和硬件创建资源
    asp.net mvc 将Enum绑定在DropDownList了
    java异常处理的throw和throws的区别
    media=screen是什么意思 有什么用?
    javascript 匿名函数
    CharSequence类型
    android使用系统资源,链接当前主题中的Style
    Android中this.*与*.this还有*.class的区别是什么?
  • 原文地址:https://www.cnblogs.com/tugenhua0707/p/5507250.html
Copyright © 2020-2023  润新知