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.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 = /(?:\{[\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模块的更多相关文章

  1. jQuery 2.0.3 源码分析 数据缓存

    历史背景: jQuery从1.2.3版本引入数据缓存系统,主要的原因就是早期的事件系统 Dean Edwards 的 ddEvent.js代码 带来的问题: 没有一个系统的缓存机制,它把事件的回调都放 ...

  2. jQuery-1.9.1源码分析系列完毕目录整理

    jQuery 1.9.1源码分析已经完毕.目录如下 jQuery-1.9.1源码分析系列(一)整体架构 jQuery-1.9.1源码分析系列(一)整体架构续 jQuery-1.9.1源码分析系列(二) ...

  3. 【转】MaBatis学习---源码分析MyBatis缓存原理

    [原文]https://www.toutiao.com/i6594029178964673027/ 源码分析MyBatis缓存原理 1.简介 在 Web 应用中,缓存是必不可少的组件.通常我们都会用 ...

  4. lodash源码分析之缓存使用方式的进一步封装

    在世界上所有的民族之中,支配着他们的喜怒选择的并不是天性,而是他们的观点. --卢梭<社会与契约论> 本文为读 lodash 源码的第九篇,后续文章会更新到这个仓库中,欢迎 star:po ...

  5. HDFS源码分析数据块校验之DataBlockScanner

    DataBlockScanner是运行在数据节点DataNode上的一个后台线程.它为所有的块池管理块扫描.针对每个块池,一个BlockPoolSliceScanner对象将会被创建,其运行在一个单独 ...

  6. HDFS源码分析数据块复制监控线程ReplicationMonitor(二)

    HDFS源码分析数据块复制监控线程ReplicationMonitor(二)

  7. jQuery-1.9.1源码分析系列(十六)ajax——响应数据处理和api整理

    ajax在得到请求响应后主要会做两个处理:获取响应数据和使用类型转化器转化数据 a.获取响应数据 获取响应数据是调用ajaxHandleResponses函数来处理. ajaxHandleRespon ...

  8. jQuery-1.9.1源码分析系列(十) 事件系统——事件体系结构

    又是一个重磅功能点. 在分析源码之前分析一下体系结构,有助于源码理解.实际上在jQuery出现之前,Dean Edwards的跨浏览器AddEvent()设计做的已经比较优秀了:而且jQuery事件系 ...

  9. jQuery-1.9.1源码分析系列(十) 事件系统——事件绑定

    事件绑定的方式有很多种.使用了jQuery那么原来那种绑定方式(elem.click = function(){...})就不推荐了,原因? 最主要的一个原因是elem.click = fn这种方式只 ...

随机推荐

  1. 为什么要用hibernate 与基于数据库表结构的项目开发

    最近开始学习hibernate,其实并不知道要学习什么,有什么用.后来问了一下同事,他就说快捷方便简单,很多事情不用自己做他会帮你做好,但是我觉得不应该是这样的,于是我就去搜了一下,就搜到了一篇帖子, ...

  2. BZOJ1178 [Apio2009]CONVENTION会议中心

    本文作者:ljh2000作者博客:http://www.cnblogs.com/ljh2000-jump/转载请注明出处,侵权必究,保留最终解释权! Description Siruseri政府建造了 ...

  3. linux时间时区设置

    用linux的时候大家可能会遇到时间不对,时区不对的情况.比如使用网上廉价的国外的linux的vps,时区是国外的.那么如何在不劳烦管理员的情况下自己动手呢?首先要了解硬件时钟与系统时钟,计算机上的B ...

  4. [NOIP2014] 提高组 洛谷P2038 无线网络发射器选址

    题目描述 随着智能手机的日益普及,人们对无线网的需求日益增大.某城市决定对城市内的公共场所覆盖无线网. 假设该城市的布局为由严格平行的129 条东西向街道和129 条南北向街道所形成的网格状,并且相邻 ...

  5. Linux Dynamic Shared Library && LD Linker

    目录 . 动态链接的意义 . 地址无关代码: PIC . 延迟版定(PLT Procedure Linkage Table) . 动态链接相关结构 . 动态链接的步骤和实现 . Linux动态链接器实 ...

  6. yocto系统介绍

    The Yocto Project is an open source collaboration project that provides templates, tools and methods ...

  7. sed学习笔记

    sed是一个文本处理工具,可以根据给出的条件,自动对文本进行处理.在使用之前,需要知道的有三点:1. sed是逐行进行处理:2. sed不对原文件进行修改:3. sed默认是将处理后的内容打印到标准输 ...

  8. Objective-C 再谈OC指针,对比C++/Java/Swift

    1.Objective-C的指针 OC一直是人感觉比较变态的一门语言,为什么呢?因为它的每个变量都是指针型,多的都几乎让人忘了那个*的存在了. 比如我定义了一个Student的Class,new了st ...

  9. Hackerrank Going to the Office

    传送门 Problem Statement Ms.Kox enjoys her job, but she does not like to waste extra time traveling to ...

  10. TypeScript Interface(接口)

    类型检查专注于解析值所具有的"形态",这是TypeScript的核心原则之一.这个有时候被称为"duck typing"或者"structural s ...