jQuery1.9.1原始碼分析--資料快取Data模組
閱讀目錄
- jQuery API中Data的基本使用方法介紹
- jQuery.acceptData(elem)原始碼分析
- jQuery.data(elem, name, data)原始碼分析
- internalRemoveData方法原始碼分析
- internalData方法的原始碼分析
- jQuery.fn.extend({data: function( key, value ) {}})原始碼分析
- jQuery.extend({removeData: function( elem, name ) {}})原始碼分析
- jQuery.cleanData(elems)原始碼分析
- jQuery.hasData(elem)原始碼分析
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 = /(?:\{[\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;