jQuery最核心的基礎設施之一——資料緩衝模組進化史

來源:互聯網
上載者:User

資料緩衝系統最早應該是jQuery1.2引入的,那時它的事件系統完成照搬DE大神的addEvent.js,而addEvent在實現有個缺憾,它把事件的回調都放到EventTarget之上,這會引發循環參考,如果EventTarget是window對象,又會引發全域汙染。有了資料緩衝系統,除了規避這兩個風險外,我們還可以有效地儲存不同方法產生的中間變數,而這些變數會對另一個模組的方法有用,解耦方法間的依賴。對於jQuery來說,它的事件複製乃至後來的列隊實現都是離不開緩衝系統。

jQuery1.2 在core模組新增了兩個靜態方法, data與removeData。data不用說,與jQuery其他方法一樣,讀寫結合。jQuery的緩衝系統是把所有資料都放$.cache之上,然後為每個要使用緩衝系統的元素節點,文檔對象與window對象分配一個UUID。UUID的屬性名稱為一個隨機的自訂屬性,"jQuery" + (new Date()).getTime(), 值為整數,從零遞增。但UUID總要附於一個對象上,如果那個對象是window,豈不是全域汙染嗎,因此jQuery內部判定它是window對象時,映射為一個叫windowData的Null 物件,然後UUID加在它之上。有了UUID,我們在首次訪問緩衝系統時,會在$.cache對象開闢一個Null 物件(緩衝體),用於放置與目標對象有關的東西。這有點像銀行開戶了,UUID的值就是存摺。removeData則會刪掉不再需要儲存資料,如果到最後,資料刪清光了,它也沒有任何索引值對,成為空白對象,jQuery就會從$.cache中刪掉此對象,並從目標對象移除UUID。

//jQuery1.2.3var 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 ] = {};        // 第三個參數不為undefined時,為寫操作        if ( data != undefined )            jQuery.cache[ id ][ name ] = data;        //如果只有一個參數,則返回緩衝對象,兩個參數則返回目標資料        return name ?  jQuery.cache[ id ][ name ] :   id;    },    removeData: function( elem, name ) {        elem = elem == window ? windowData :   elem;        var id = elem[ expando ];        if ( name ) {//移除目標資料            if ( jQuery.cache[ id ] ) {                delete jQuery.cache[ id ][ name ];                name = "";                for ( name in jQuery.cache[ id ] )                    break;                //遍曆緩衝體,如果不為空白,那name會被改寫,如果沒有被改寫,則!name 為true,                //從而引發再次調用此方法,但這次是只傳一個參數,移除緩衝體,                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的自訂事件的觸發邏輯。

1.3中,資料緩衝系統終於獨立成一個模組data.js(內部開發時的劃分),並添加了兩組方法,命名空間上的queue與dequeue,原型上的queue與dequeue。queue的目的很明顯,就是緩衝一組資料,為動畫模組服務。dequeue是從一組資料中刪掉一個。

  //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) )//確保儲存的是一個數組                  q = jQuery.data( elem, type, jQuery.makeArray(data) );              else if( data )//然後往這個資料加東西                  q.push( data );          }          return q;      },      dequeue: function( elem, type ){          var queue = jQuery.queue( elem, type ),          fn = queue.shift();//然後刪掉一個,早期它是放置動畫的回調,刪掉它就call一下,          // 但沒有做是否為函數的判定,估計也沒有寫到文檔中,為內部使用          if( !type || type === "fx" )              fn = queue[0];          if( fn !== undefined )              fn.call(elem);      }  })

fx模組animate方法的調用樣本:

    //each是平行處理多個動畫,queue是一個接一個處理多個動畫  this[ optall.queue === false ? "each" : "queue" ](function(){ /*略*/})

在元素上添加自訂屬性,還會引發一個問題。如果我們對這個元素進行拷貝,就會將此屬性也會複製過去,導致兩個元素都有相同的UUID值,出現資料被錯誤操作的情況。jQuery早期的複製節點實現非常簡單,如果元素的cloneNode方法不會複製事件就使用cloneNode,否則使用元素的outerHTML,或父節點的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發現IE如果對於object, ember, applet這三個古老的用於接入外部資源的標籤可能會拋錯。由於舊式IE的元素節點只是COM的封裝,一旦引入資源後,它就會變成那種資源的執行個體,而它們會有嚴格的存取控制,不能像普通的JS對象那樣隨意新增成員。於是jQuery便一刀換,但凡是這三種標籤,就不為它快取資料。jQuery弄了一個叫noData的hash,用於檢測元素節點的標籤。

  noData: {      "embed": true,      "object": true,      "applet": true  },  //代碼防禦          if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) {      return;  }

jQuery1.4還對$.data進行改進,允許第二個參數為對象,方便儲存多個資料。UUID對應的自訂屬性expando 也放進命名空間之下了。queue與dequeue方法被剝離成一個新模組。

  

jQuery1.43帶來三項改進。

首先是添加changeData自訂方法。不過這套方法沒有什麼銷量,只是產品經理的自戀吧。

  

檢測元素節點是否支援添加自訂屬性的邏輯被獨立成一個叫acceptData的方法。因為jQuery團隊發現當object標籤載入的flash資源,它還是可以添加自訂屬性的,於是決定對這種情況網開一面。IE在載入flash時,需要對object指定一個叫classId的屬性,值為clsid:D27CDB6E-AE6D-11cf-96B8-444553540000,因此檢測邏輯就變得非常複雜,由於data, removeData都要用到,獨立出來有效節省位元。

HTML5對人們隨便添加自訂屬性的行為做出回應,新增一種叫"data-*"的緩衝機制。當使用者佈建的屬性以"data-"開頭,它們會被儲存到元素節點的dataset對象上。這就導致人們可能用HTML5方便快取資料,也可能用jQuery的緩衝系統儲存資料,那麼data方法就變得有點不中用了。於是jQuery在原型上的data做了增強,當使用者第一次訪問此元素節點,會遍曆它所有"data-"開頭的自訂屬性(為了照顧舊式IE,不能直接遍曆dataset),把它們放到jQuery的緩衝體中。那麼當使用者取資料時,會先從緩衝系統中,沒有再使用setAttribute訪問"data-"自訂屬性。但HTML5的緩衝系統非常弱,只能儲存字串(這當然是出於循環參考的考量),於是jQuery會將它們還原為各種資料類型,如"null",, "false", "true"變成null, false, true, 符合數字格式的字串會轉換成數字,如果它是以"{"開頭"}"結尾則嘗試轉成一個對象。

  //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;          }      }  }

jQuery1.5也帶來三項改進。當時jQuery已經在1.42打敗Prototype.js,如日中天,馬太效應,使用者量暴增。它的重點改為提升效能,進入fix bug階段(使用者多,相當於免費的測試員就越多,測試覆蓋面就越大)。

改進expando,原來是基於時間截,現在是版本號碼加隨機數。因此使用者可能在一個頁面引入多個版本的jQuery。

是否有此資料的邏輯被抽出成一個hasData方法,處理HTML5的"data-*"屬性也被抽出成一個私人方法dataAttr。它們都是為了邏輯顯得更清晰。dataAttr使用JSON.parse,由於這個JSON可能是JSON2.js引入的,而JSON2.js有個非常糟糕的地方,就是為一系列原生類型添加了toJSON方法,導致for in 迴圈判定是否為空白對象出錯。jQuery被逼搞了個isEmptyDataObject方法做處理。

jQuery的資料緩衝系統本來就是為事件系統服務而分化出來的,到後來,它是內部眾多模組的基礎設施。換言之,它內部會儲存許多架構使用者的變數(系統資料),但一旦它公開到文檔中,使用者也會使用data儲存他們務業中使用的資料(使用者資料)。以前,使用者小,變數名衝突的可能性比較少,加之jQuery為這些系統資料精挑了一些不常用的名字,__class__, __change__或加個尾碼什麼的,沒有收到什麼投訴。當jQuery成為世界級的著名架構後,使用者資料名幹掉系統資料名,導致事件系統或其他什麼模組癱瘓就時有發生。jQuery開始對緩衝體進行改造,原來就是一個對象,什麼資料都往裡面拋。現在它就這個緩衝體內開闢一個子物件,鍵名為隨機的jQuery.expando值,如果是系統資料就存到裡面去。但events系統資料為了向前相容起見,還是直接放到緩衝體之上。至於,如何區分是系統資料,非常簡單,直接在data方法添加第四個參數,真值時為系統資料。removeData時也相應提供第三個參數,用於刪除系統資料。還新設了一個_data方法,專門用於作業系統資料。下面就是緩衝體的結構圖:

  var cache = {     jQuery14312343254:{/*放置系統資料*/}     events: {/"放置事件名與它對應的回調列表"/}     /*這裡放置使用者資料*/}

jQuery1.7對緩衝體做了改進,系統變數變放置data對象中,為此判定緩衝體為空白也要做相應的改進,現在要跳過toJSON與data。新結構如下:

var cache = {     data:{/*放置使用者資料*/}     /*這裡放置系統資料*/}

jQuery1.8曾添加一個叫deleteIds的數組,用於重用UUID。UUID的值從1.8起不用jQuery.uuid的了,改用jQuery.guid遞增產生。重大的改進在jQuery1.83後,操作資料的實現被抽出為私人方法,命名空間與原型上的方法只是一個代理,並分成兩組方法,操作使用者資料的data, removeData,作業系統資料的_data,_removeData。現在光是緩衝系統就是一個龐大家族了。

說到底,資料緩衝就是在目標對象與緩衝體間建立一對一的關係,然後在緩衝體上操作資料,複雜度都集在前者。而在一個普通JS對象進行增刪改查某屬性從來沒有難度,使用者怎麼也玩不出花招。從軟體設計原則上看,這也是最好的結果(吻合KISS原則與職責單一則)。

如何使用?

//執行個體方法$(node).data("xxx","yyy")//寫方法,一次寫入一個$(node).data({//一次寫入多個   "aaa":1,   "bbb":2})//靜態方法$.data(node,"ccc",3)$._data(node,"ccc",3)//由於是放在不同對象上,因此不會覆蓋上面的cc$(node).data("xxx");//讀方法$.data(node, "xxx")$._data(node, "ccc")$.removeData(node, "xxx")$._removeData(node, "ccc")
  

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.