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); 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")); 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.expandoc. 如果数据缓存对象关联的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 = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/, 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;
jQuery1.9.1源码分析--数据缓存Data模块的更多相关文章
- jQuery 2.0.3 源码分析 数据缓存
历史背景: jQuery从1.2.3版本引入数据缓存系统,主要的原因就是早期的事件系统 Dean Edwards 的 ddEvent.js代码 带来的问题: 没有一个系统的缓存机制,它把事件的回调都放 ...
- jQuery-1.9.1源码分析系列完毕目录整理
jQuery 1.9.1源码分析已经完毕.目录如下 jQuery-1.9.1源码分析系列(一)整体架构 jQuery-1.9.1源码分析系列(一)整体架构续 jQuery-1.9.1源码分析系列(二) ...
- 【转】MaBatis学习---源码分析MyBatis缓存原理
[原文]https://www.toutiao.com/i6594029178964673027/ 源码分析MyBatis缓存原理 1.简介 在 Web 应用中,缓存是必不可少的组件.通常我们都会用 ...
- lodash源码分析之缓存使用方式的进一步封装
在世界上所有的民族之中,支配着他们的喜怒选择的并不是天性,而是他们的观点. --卢梭<社会与契约论> 本文为读 lodash 源码的第九篇,后续文章会更新到这个仓库中,欢迎 star:po ...
- HDFS源码分析数据块校验之DataBlockScanner
DataBlockScanner是运行在数据节点DataNode上的一个后台线程.它为所有的块池管理块扫描.针对每个块池,一个BlockPoolSliceScanner对象将会被创建,其运行在一个单独 ...
- HDFS源码分析数据块复制监控线程ReplicationMonitor(二)
HDFS源码分析数据块复制监控线程ReplicationMonitor(二)
- jQuery-1.9.1源码分析系列(十六)ajax——响应数据处理和api整理
ajax在得到请求响应后主要会做两个处理:获取响应数据和使用类型转化器转化数据 a.获取响应数据 获取响应数据是调用ajaxHandleResponses函数来处理. ajaxHandleRespon ...
- jQuery-1.9.1源码分析系列(十) 事件系统——事件体系结构
又是一个重磅功能点. 在分析源码之前分析一下体系结构,有助于源码理解.实际上在jQuery出现之前,Dean Edwards的跨浏览器AddEvent()设计做的已经比较优秀了:而且jQuery事件系 ...
- jQuery-1.9.1源码分析系列(十) 事件系统——事件绑定
事件绑定的方式有很多种.使用了jQuery那么原来那种绑定方式(elem.click = function(){...})就不推荐了,原因? 最主要的一个原因是elem.click = fn这种方式只 ...
随机推荐
- MicrosoftWord2013基本用法
MicrosoftWord2013基本用法 Word联机使用 自定义工作区 单击"文件"选项,单击"自定义功能区".显示的就是我们编辑文档时上方的工具栏所有选项 ...
- Chapter 3: Develop the user experience
Plan for search engine optimization and accessibility 使用analytical tools分析HTML,如SEO toolkit from MS, ...
- mysql创建视图
CREATE ALGORI`sync_user`CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER V ...
- IOS弹出视图 LewPopupViewController
LewPopupViewController是一款IOS弹出视图软件.iOS 下的弹出视图.支持iPhone/iPad. 软件截图 使用方法 弹出视图 1 2 3 4 5 PopupView *vie ...
- enableEventValidation
回发或回调参数无效.在配置中使用 <pages enableEventValidation="true"/> 或在页面中使用 <%@ Page EnableEve ...
- Objective-c Category(类别)
category是Objective-c里面最常用的功能之一. category可以为已经存在的类增加方法,而不需要增加一个子类. 类别接口的标准语法格式如下: #import "类名.h& ...
- hdu 4710 Balls Rearrangement 数论
这个公倍数以后是循环的很容易找出来,然后循环以内的计算是打表找的规律,规律比较难表述,自己看代码吧.. #include <iostream> #include <cstdio> ...
- C# 的四舍五入
c#的四舍五入有两种情况: 1.常规四舍五入 (decimal).ToString("f2") 2.四舍六入五取偶 除1里面的其他方式四舍五入都是四舍六入五取偶.
- 手工搭建基于ABP的框架(2) - 访问数据库
为了防止不提供原网址的转载,特在这里加上原文链接: http://www.cnblogs.com/skabyy/p/7517397.html 本篇我们实现数据库的访问.我们将实现两种数据库访问方法来访 ...
- 性能测试-3.Fiddler进行弱网测试
fiddler模拟限速的原理(原文地址) 我们可以通过fiddler来模拟限速,因为fiddler本来就是个代理,它提供了客户端请求前和服务器响应前的回调接口,我们可以在这些接口里 面自定义一些逻辑. ...