jQuery 數(shù)據(jù)緩存模塊進化史詳細介紹
數(shù)據(jù)緩存系統(tǒng)最早應該是jQuery1.2引入的,那時它的事件系統(tǒng)完成照搬DE大神的addEvent.js,而addEvent在實現(xiàn)有個缺憾,它把事件的回調都放到EventTarget之上,這會引發(fā)循環(huán)引用,如果EventTarget是window對象,又會引發(fā)全局污染。有了數(shù)據(jù)緩存系統(tǒng),除了規(guī)避這兩個風險外,我們還可以有效地保存不同方法產(chǎn)生的中間變量,而這些變量會對另一個模塊的方法有用,解耦方法間的依賴。對于jQuery來說,它的事件克隆乃至后來的列隊實現(xiàn)都是離不開緩存系統(tǒng)。
jQuery1.2 在core模塊新增了兩個靜態(tài)方法, data與removeData。data不用說,與jQuery其他方法一樣,讀寫結合。jQuery的緩存系統(tǒng)是把所有數(shù)據(jù)都放$.cache之上,然后為每個要使用緩存系統(tǒng)的元素節(jié)點,文檔對象與window對象分配一個UUID。UUID的屬性名為一個隨機的自定義屬性,"jQuery" + (new Date()).getTime(), 值為整數(shù),從零遞增。但UUID總要附于一個對象上,如果那個對象是window,豈不是全局污染嗎,因此jQuery內部判定它是window對象時,映射為一個叫windowData的空對象,然后UUID加在它之上。有了UUID,我們在首次訪問緩存系統(tǒng)時,會在$.cache對象開辟一個空對象(緩存體),用于放置與目標對象有關的東西。這有點像銀行開戶了,UUID的值就是存折。removeData則會刪掉不再需要保存數(shù)據(jù),如果到最后,數(shù)據(jù)刪清光了,它也沒有任何鍵值對,成為空對象,jQuery就會從$.cache中刪掉此對象,并從目標對象移除UUID。
//jQuery1.2.3
var expando = "jQuery" + (new Date()).getTime(), uuid = 0, windowData = {};
jQuery.extend({
cache: {},
data: function( elem, name, data ) {
elem = elem == window ? windowData : elem;//對window對象做特別處理
var id = elem[ expando ];
if ( !id ) //如果沒有UUID則新設一個
id = elem[ expando ] = ++uuid;
//如果沒有在$.cache中開戶,則先開戶
if ( name && !jQuery.cache[ id ] )
jQuery.cache[ id ] = {};
// 第三個參數(shù)不為undefined時,為寫操作
if ( data != undefined )
jQuery.cache[ id ][ name ] = data;
//如果只有一個參數(shù),則返回緩存對象,兩個參數(shù)則返回目標數(shù)據(jù)
return name ? jQuery.cache[ id ][ name ] : id;
},
removeData: function( elem, name ) {
elem = elem == window ? windowData : elem;
var id = elem[ expando ];
if ( name ) {//移除目標數(shù)據(jù)
if ( jQuery.cache[ id ] ) {
delete jQuery.cache[ id ][ name ];
name = "";
for ( name in jQuery.cache[ id ] )
break;
//遍歷緩存體,如果不為空,那name會被改寫,如果沒有被改寫,則!name 為true,
//從而引發(fā)再次調用此方法,但這次是只傳一個參數(shù),移除緩存體,
if ( !name )
jQuery.removeData( elem );
}
} else {
//移除UUID,但IE下對元素使用delete會拋錯
try {
delete elem[ expando ];
} catch(e){
if ( elem.removeAttribute )
elem.removeAttribute( expando );
}//注銷賬戶
delete jQuery.cache[ id ];
}
}
})
jQuery在1.2.3中添加了兩個同名的原型方法data與removeData,目的是方便鏈式操作與集化操作。并在data中添加getData, setData的自定義事件的觸發(fā)邏輯。
//jQuery1.3
jQuery.extend({
queue: function( elem, type, data ) {
if ( elem ){
type = (type || "fx") + "queue";
var q = jQuery.data( elem, type );
if ( !q || jQuery.isArray(data) )//確保儲存的是一個數(shù)組
q = jQuery.data( elem, type, jQuery.makeArray(data) );
else if( data )//然后往這個數(shù)據(jù)加東西
q.push( data );
}
return q;
},
dequeue: function( elem, type ){
var queue = jQuery.queue( elem, type ),
fn = queue.shift();//然后刪掉一個,早期它是放置動畫的回調,刪掉它就call一下,
// 但沒有做是否為函數(shù)的判定,估計也沒有寫到文檔中,為內部使用
if( !type || type === "fx" )
fn = queue[0];
if( fn !== undefined )
fn.call(elem);
}
})
fx模塊animate方法的調用示例:
//each是并行處理多個動畫,queue是一個接一個處理多個動畫
this[ optall.queue === false ? "each" : "queue" ](function(){ /*略*/})
在元素上添加自定義屬性,還會引發(fā)一個問題。如果我們對這個元素進行拷貝,就會將此屬性也會復制過去,導致兩個元素都有相同的UUID值,出現(xiàn)數(shù)據(jù)被錯誤操作的情況。jQuery早期的復制節(jié)點實現(xiàn)非常簡單,如果元素的cloneNode方法不會復制事件就使用cloneNode,否則使用元素的outerHTML,或父節(jié)點的innerHTML,用clean方法解析一個新元素出來。但outerHTML與innerHTML都會顯式屬性寫在里面,因此需要用正則把它們清除掉。
//jQuery1.3.2 core.js clone方法
var ret = this.map(function(){
if ( !jQuery.support.noCloneEvent && !jQuery.isXMLDoc(this) ) {
var html = this.outerHTML;
if ( !html ) {
var div = this.ownerDocument.createElement("div");
div.appendChild( this.cloneNode(true) );
html = div.innerHTML;
}
return jQuery.clean([html.replace(/ jQuery\d+="(?:\d+|null)"/g, "").replace(/^\s*/, "")])[0];
} else
return this.cloneNode(true);
});
jQuery1.4發(fā)現(xiàn)IE如果對于object, ember, applet這三個古老的用于接入外部資源的標簽可能會拋錯。由于舊式IE的元素節(jié)點只是COM的包裝,一旦引入資源后,它就會變成那種資源的實例,而它們會有嚴格的訪問控制,不能像普通的JS對象那樣隨意添加成員。于是jQuery便一刀換,但凡是這三種標簽,就不為它緩存數(shù)據(jù)。jQuery弄了一個叫noData的hash,用于檢測元素節(jié)點的標簽。
noData: { "embed": true,
"object": true,
"applet": true },
//代碼防御
if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) {
return;
}
jQuery1.4還對$.data進行改進,允許第二個參數(shù)為對象,方便儲存多個數(shù)據(jù)。UUID對應的自定義屬性expando 也放進命名空間之下了。queue與dequeue方法被剝離成一個新模塊。
jQuery1.43帶來三項改進。
首先是添加changeData自定義方法。不過這套方法沒有什么銷量,只是產(chǎn)品經(jīng)理的自戀吧。
HTML5對人們隨便添加自定義屬性的行為做出回應,新增一種叫"data-*"的緩存機制。當用戶設置的屬性以"data-"開頭,它們會被保存到元素節(jié)點的dataset對象上。這就導致人們可能用HTML5方便緩存數(shù)據(jù),也可能用jQuery的緩存系統(tǒng)保存數(shù)據(jù),那么data方法就變得有點不中用了。于是jQuery在原型上的data做了增強,當用戶第一次訪問此元素節(jié)點,會遍歷它所有"data-"開頭的自定義屬性(為了照顧舊式IE,不能直接遍歷dataset),把它們放到jQuery的緩存體中。那么當用戶取數(shù)據(jù)時,會先從緩存系統(tǒng)中,沒有再使用setAttribute訪問"data-"自定義屬性。但HTML5的緩存系統(tǒng)非常弱,只能保存字符串(這當然是出于循環(huán)引用的考量),于是jQuery會將它們還原為各種數(shù)據(jù)類型,如"null",, "false", "true"變成null, false, true, 符合數(shù)字格式的字符串會轉換成數(shù)字,如果它是以"{"開頭"}"結尾則嘗試轉成一個對象。
//jQuery1.43 $.fn.data
rbrace = /^(?:\{.*\}|\[.*\])$/;
if ( data === undefined && this.length ) {
data = jQuery.data( this[0], key );
if ( data === undefined && this[0].nodeType === 1 ) {
data = this[0].getAttribute( "data-" + key );
if ( typeof data === "string" ) {
try {
data = data === "true" ? true :
data === "false" ? false :
data === "null" ? null :
!jQuery.isNaN( data ) ? parseFloat( data ) :
rbrace.test( data ) ? jQuery.parseJSON( data ) :
data;
} catch( e ) {}
} else {
data = undefined;
}
}
}
改進expando,原來是基于時間截,現(xiàn)在是版本號加隨機數(shù)。因此用戶可能在一個頁面引入多個版本的jQuery。
是否有此數(shù)據(jù)的邏輯被抽出成一個hasData方法,處理HTML5的"data-*"屬性也被抽出成一個私有方法dataAttr。它們都是為了邏輯顯得更清晰。dataAttr使用JSON.parse,由于這個JSON可能是JSON2.js引入的,而JSON2.js有個非常糟糕的地方,就是為一系列原生類型添加了toJSON方法,導致for in 循環(huán)判定是否為空對象出錯。
jQuery的數(shù)據(jù)緩存系統(tǒng)本來就是為事件系統(tǒng)服務而分化出來的,到后來,它是內部眾多模塊的基礎設施。換言之,它內部會儲存許多框架用戶的變量(系統(tǒng)數(shù)據(jù)),但一旦它公開到文檔中,用戶也會使用data保存他們務業(yè)中使用的數(shù)據(jù)(用戶數(shù)據(jù))。以前,用戶小,變量名沖突的可能性比較少,加之jQuery為這些系統(tǒng)數(shù)據(jù)精挑了一些不常用的名字,__class__, __change__或加個后綴什么的,沒有收到什么投訴。當jQuery成為世界級的著名框架后,用戶數(shù)據(jù)名干掉系統(tǒng)數(shù)據(jù)名,導致事件系統(tǒng)或其他什么模塊癱瘓就時有發(fā)生。jQuery開始對緩存體進行改造,原來就是一個對象,什么數(shù)據(jù)都往里面拋?,F(xiàn)在它就這個緩存體內開辟一個子對象,鍵名為隨機的jQuery.expando值,如果是系統(tǒng)數(shù)據(jù)就存到里面去。但events系統(tǒng)數(shù)據(jù)為了向前兼容起見,還是直接放到緩存體之上。至于,如何區(qū)分是系統(tǒng)數(shù)據(jù),非常簡單,直接在data方法添加第四個參數(shù),真值時為系統(tǒng)數(shù)據(jù)。removeData時也相應提供第三個參數(shù),用于刪除系統(tǒng)數(shù)據(jù)。還新設了一個_data方法,專門用于操作系統(tǒng)數(shù)據(jù)。下面就是緩存體的結構圖:
var cache = {
jQuery14312343254:{/*放置系統(tǒng)數(shù)據(jù)*/}
events: {/"放置事件名與它對應的回調列表"/}
/*這里放置用戶數(shù)據(jù)*/
}
jQuery1.7對緩存體做了改進,系統(tǒng)變量變放置data對象中,為此判定緩存體為空也要做相應的改進,現(xiàn)在要跳過toJSON與data。新結構如下:
var cache = {
data:{/*放置用戶數(shù)據(jù)*/}
/*這里放置系統(tǒng)數(shù)據(jù)*/
}
jQuery1.8曾添加一個叫deleteIds的數(shù)組,用于重用UUID,但曇花一現(xiàn)。UUID的值從1.8起不用jQuery.uuid的了,改用jQuery.guid遞增生成。重大的改進在jQuery1.83后,操作數(shù)據(jù)的實現(xiàn)被抽出為私有方法,命名空間與原型上的方法,并分成兩組方法,操作用戶數(shù)據(jù)的data, removeData,操作系統(tǒng)數(shù)據(jù)的_data,_removeData?,F(xiàn)在光是緩存系統(tǒng)就是一個龐大家族了。

說到底,數(shù)據(jù)緩存就是在目標對象與緩存體間建立一對一的關系,然后在緩存體上操作數(shù)據(jù),復雜度都集在前者。而在一個普通JS對象進行增刪改查某屬性從來沒有難度,用戶怎么也玩不出花招。從軟件設計原則上看,這也是最好的結果(吻合KISS原則與職責單一則)。
相關文章
jQuery實現(xiàn)點擊按鈕彈出可關閉層的浮動層插件
這篇文章主要介紹了jQuery實現(xiàn)點擊按鈕彈出可關閉層的浮動層插件,具有點擊彈出帶有遮罩層的浮動層效果,且浮動層可拖動、可關閉,需要的朋友可以參考下2015-09-09jQuery動態(tài)添加刪除select項(實現(xiàn)代碼)
以下是對jQuery動態(tài)添加刪除select項的實現(xiàn)代碼進行了詳細的分析介紹,需要的朋友可以過來參考下2013-09-09jquery mobile界面數(shù)據(jù)刷新的實現(xiàn)方法
下面小編就為大家?guī)硪黄猨query mobile界面數(shù)據(jù)刷新的實現(xiàn)方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-05-05jQuery擴展實現(xiàn)text提示還能輸入多少字節(jié)的方法
這篇文章主要介紹了jQuery擴展實現(xiàn)text提示還能輸入多少字節(jié)的方法,涉及jQuery擴展及字符串操作相關技巧,需要的朋友可以參考下2016-11-11jQuery動態(tài)改變圖片顯示大小(修改版)的實現(xiàn)思路及代碼
這篇文章主要介紹了jQuery動態(tài)改變圖片顯示大小(修改版)的實現(xiàn)思路及代碼,有需要的朋友可以參考一下2013-12-12