jQuery 源碼分析(十) 數據緩存模塊 data詳解

来源:https://www.cnblogs.com/greatdesert/archive/2019/10/14/11609111.html
-Advertisement-
Play Games

jQuery的數據緩存模塊以一種安全的方式為DOM元素附加任意類型的數據,避免了在JavaScript對象和DOM元素之間出現迴圈引用,以及由此而導致的記憶體泄漏。 數據緩存模塊為DOM元素和JavaScript對象提供了統一的數據設置、讀取和移除方法,在jQuery內部還為隊列模塊、動畫模塊、樣式操 ...


jQuery的數據緩存模塊以一種安全的方式為DOM元素附加任意類型的數據,避免了在JavaScript對象和DOM元素之間出現迴圈引用,以及由此而導致的記憶體泄漏。

數據緩存模塊為DOM元素和JavaScript對象提供了統一的數據設置、讀取和移除方法,在jQuery內部還為隊列模塊、動畫模塊、樣式操作模塊、事件系統提供基礎功能,負責維護這些模塊運行時的內部數據。

 writer by:大沙漠 QQ:22969969

對於DOM元素和JavaScript對象,數據的存儲位置是不同的,如下:

  • 對於DOM元素jQuery直接把數據存儲在jQuery.cache中
  • 對於JavaScript對象,垃圾回收機制能夠自動發生,因此數據可以直接存儲在JavaScript對象中。

另外為了避免jQuery內部使用的數據和用戶自定義的數據發生衝突,分為內部數據緩存對象和自定義數據緩存對象

  • 內部緩存對象      ;jQuery內部使用        ;DOM元素:存儲在$.cache[elem[$.expando]]                 ;JavaScript對象:obj[$.expando]
  • 自定義緩存對象     ;給用戶使用的            ;DOM元素:存儲在$.cache[elem[$.expando]].data        ;JavaScript對象:obj[$.expando].data

jQuery的靜態方法含有如下API:

  • $.cache              ;DOM元素的數據緩存對象,所有DOM元素存儲的數據都會存儲在該對象里
  • $.uuid                 ;唯一id種子,初始值為0,當數據存儲在DOM元素上時用到,元素的$.expando屬性的值等於最新的$.uuid加1
  • $.expando                ;頁面中每個jQuery副本的唯一標識,只有刷新頁面才會發生變化。格式:jQuery+版本號+隨機數
  • $.acceptData(elem)           ;判斷DOM元素elem是否可以設置數據,elem是一個DOM節點
  • $.hasData(elem)           ;判斷elem是否有關聯的數據
  • $.data(elem, name, data,pvt)  ;設置或返回DOM或JavaScript對象的數據。

                  ·elem是DOM元素或JavaScript對象。

                  ·name是要設置或讀取的數據名,也可以是包含鍵值對的對象。

                  ·data是要設置的數據值,可以是任意數據。

                  ·pvt表示操作的是否為內部數據,預設為false

  • $._data(elem, name, data)   ;設置、讀取內部數據,內部代碼就一句return jQuery.data( elem, name, data, true )
  • $.removeData(elem, name, pvt) ;移除通過$.data()設置的數據,pvt表示是否為內部數據
  •  $.cleanData(elem)         ;移除多個DOM元素的全部數據和事件

jQuery/$ 實例方法(可以通過jQuery實例調用的):

  • data(key,value)        ;設置/讀取自定義數據
  • removeData(key)        ;移除匹配元素的自定義數據,key可以是一個字元換或數組,表示屬性或屬性列表

為DOM元素存儲數據時,比較特別,jQuery首先會在該DOM上添加一個名為$.expando的屬性,值是一個唯一的id,等於++$.uuid(jQuery的一個內置屬性),$.uuid是一個整型值,初始值為0。為該DOM添加屬性之後還會把這個id作為屬性添加到全局緩存對象jQuery.cache中,對應的屬性值是一個JavaScript對象,該對象是DOM元素的數據緩存對象

例如:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="http://libs.baidu.com/jquery/1.7.1/jquery.min.js"></script>
</head>
<body>
    <p>123</p>
    <script>
        var p = document.getElementsByTagName('p')[0];
        $.data(p,'age',25,true);                                //設置內部數據age=25,這是直接定義在數據緩存對象上的。等價於$._data(p,'age',25);
        $.data(p,'age',23);                                     //設置自定義數據age=23,等價於$.data(p,'age',23,false),這是定義在數據緩存對象的data屬性對象上
        console.log($.data(p,undefined,undefined,true));        //輸出: Object { data={ age=23},  age=25}    ;獲取數據緩存對象。    
        console.log($.data(p));                                 //輸出: Object { age=23}             ;獲取自定義緩存對象,其實就是$.data(p,undefined,undefined,true)對象的data屬性
        console.log($.cache[p[$.expando]].data === $.data(p));  //輸出true,從這裡可以看出$.data(p)獲取的就是自定義緩存對象,也就是數據緩存對象的data屬性對象
    </script>    
</body>
</html>

輸出如下:

 

源碼分析


對於數據緩存模塊的靜態方法來說,它是以jQuery.extend({})函數直接掛載到jQuery里的,如下:

jQuery.extend({
  cache: {},                                //DOM元素的數據緩存對象

  // Please use with caution
  uuid: 0,

  // Unique for each copy of jQuery on the page
  // Non-digits removed to match rinlinejQuery
  expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ),    //頁面中每個jQuery副本的唯一標識

  // The following elements throw uncatchable exceptions if you
  // attempt to add expando properties to them.
  noData: {                                   //存放了不支持擴展屬性的embed、object、applet元素的節點名稱
    "embed": true,
    // Ban all objects except for Flash (which handle expandos)
    "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",
    "applet": true
  },

  hasData: function( elem ) {                 //判斷一個DOM元素或JavaScript對象是否有與之關聯的數據
    elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];     //如果是元素節點(有nodeType屬性)則判斷在jQuery.cache中是否有jQuery.expando屬性,否則認為是JavaScript對象,判斷是否有jQuery.expando屬性。
    return !!elem && !isEmptyDataObject( elem );                                              //如果elem存在且含有數據緩存則返回true,isEmptyDataObject是個jQuery內部的工具函數
  },

  data: function( elem, name, data, pvt /* Internal Use Only */ ) {
    /**/
  },

  removeData: function( elem, name, pvt /* Internal Use Only */ ) {
    /**/
  },

  // For internal use only.
  _data: function( elem, name, data ) {       //設置、讀取內部數據,就是調用jQuery.data(),並設置第四個參數為true
    return jQuery.data( elem, name, data, true );
  },

  // A method for determining if a DOM node can handle the data expando
  acceptData: function( elem ) {              //判斷參數elem是否可以設置數據,返回true則可以設置,返回false則不可以
    if ( elem.nodeName ) {
      var match = jQuery.noData[ elem.nodeName.toLowerCase() ];

      if ( match ) {
        return !(match === true || elem.getAttribute("classid") !== match);
      }
    }

    return true;
  }
});

我們主要看一下$.data()是怎麼設置數據的,懂了怎麼設置數據,removeData也就懂了,如下:

data: function( elem, name, data, pvt /* Internal Use Only */ ) {       //設置、讀取自定義數據、內部數據
  if ( !jQuery.acceptData( elem ) ) {               //檢查elem元素是否支持設置數據,如果jQuery.acceptData()函數返回false表示不允許設置數據
    return;                                           //則直接返回,不繼續操作
  }

  var privateCache, thisCache, ret,                 //privateCache預設指向數據緩存對象(如果pvt參數未設置或者為false則指向自定義數據),thisCache表示自定義數據緩存對象,如果pvt是true,則privateCache和thisCache都指向數據緩存對象都指向數據緩存對象。ret是讀取時的返回值
    internalKey = jQuery.expando,                   //jQuery.expando頁面中每個jQuery副本的唯一標識,把它賦值給internalKey是為了減少拼寫字數和縮短作用域鏈查找。
    getByName = typeof name === "string",           //getByName表示name是否為字元串

    // 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,                         //isNode表示elem是否為DOM元素

    // 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,           //如果是DOM元素則存儲在$.cache中,如果是JavaScript對象則存儲在該對象本身 

    // 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,
    isEvents = name === "events";

  // 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] || (!isEvents && !pvt && !cache[id].data)) && getByName && data === undefined ) {   //如果是讀取數據但沒有數據,則返回,避免做不必要的工作,if語句中的符合表達式可以分兩個部分,後一部分是getByName && data === undefined,表示,如果name是字元串且data沒有設置,則說明是在讀數據。 前一部分(!id || !cache[id] || (!isEvents && !pvt && !cache[id].data)表示,如果id不存在說明沒有該屬性,如果cache[id]不存在則說明沒有該數據
    return;
  }
  /*執行到這裡有兩種情況:1.存儲數據 2.讀取數據且數據存在*/   
  if ( !id ) {                                      //如果id不存在,則分配一個
    // Only DOM nodes need a new unique ID for each element since their data
    // ends up in the global cache
    if ( isNode ) {                                   //如果是DOM元素
      elem[ internalKey ] = id = ++jQuery.uuid;         //jQuery.uuid會自動加1,並附在DOM元素上
    } else {
      id = internalKey;                               //否則關聯ID就是jQuery.expando
    }
  }

  if ( !cache[ id ] ) {                           //如果DOM對象或JavaScript對象對應的數據緩存對象不存在則初始化為一個空對象
    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" ) { //如果name是對象或者函數(函數好像不可以,只能是對象),則批量把參數name中的屬性合併到已有的數據緩存對象上,即批量設置數據
    if ( pvt ) {
      cache[ id ] = jQuery.extend( cache[ id ], name );
    } else {
      cache[ id ].data = jQuery.extend( cache[ id ].data, name );
    }
  }

  privateCache = thisCache = cache[ id ];                         //設置privateCache和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 ) {                                                   //如果參數pvt是false或者未設置,則設置thisCache指向自定義數據,
    if ( !thisCache.data ) {                                        //如果數據緩存對象thisCache.data不存在則先將其初始化為空對象。
      thisCache.data = {};
    }

    thisCache = thisCache.data;
  }

  if ( data !== undefined ) {                                     //如果data不是undefined,則把參數data設置到屬性name上,這裡統一把參數name轉換成了駝峰式,這樣在讀取的時候不管是連字元串還是駝峰式就都不會出錯。
    thisCache[ jQuery.camelCase( name ) ] = data;
  }

  // Users should not attempt to inspect the internal events object using jQuery.data,
  // it is undocumented and subject to change. But does anyone listen? No.
  if ( isEvents && !thisCache[ name ] ) {
    return privateCache.events;
  }

  // Check for both converted-to-camel and non-converted data property names
  // If a data property was specified
  if ( getByName ) {                                            //如果參數name是字元串,則讀取單個數據

    // First Try to find as-is property data
    ret = thisCache[ name ];                                      //先嘗試讀取參數name對應的數據

    // Test for null|undefined property data  
    if ( ret == null ) {                                          //如果沒有讀取到則把參數name轉換為駝峰式再次嘗試讀取

      // Try to find the camelCased property
      ret = thisCache[ jQuery.camelCase( name ) ];
    }
  } else {
    ret = thisCache;                                          //如果參數2不是字元串,則返回數據緩存對象。
  }

  return ret;                                                 //最後返回ret
},

這樣就完成數據的設置的,對於jQuery實例上的方法如下:

jQuery.fn.extend({
  data: function( key, value ) {      //設置、讀取自定義數據,解析html5屬性data- key是要設置或讀取的數據名,或者是含有鍵值對的對象,value是要設置的數據值,可以是任意類型
    var parts, attr, name,
      data = null;

    if ( typeof key === "undefined" ) {       //如果未傳入參數,即參數格式是.data(),則獲取第一個匹配元素關聯的數據緩存對象(即獲得全部數據)
      if ( this.length ) {                      //如果該jQuery對象有匹配的元素
        data = jQuery.data( this[0] );            //獲取第一個元素的數據緩存對象

        if ( this[0].nodeType === 1 && !jQuery._data( this[0], "parsedAttrs" ) ) {    //這裡是解析html5里的data-屬性的,可以先略過
          attr = this[0].attributes;
          for ( var i = 0, l = attr.length; i < l; i++ ) {
            name = attr[i].name;

            if ( name.indexOf( "data-" ) === 0 ) {
              name = jQuery.camelCase( name.substring(5) );

              dataAttr( this[0], name, data[ name ] );
            }
          }
          jQuery._data( this[0], "parsedAttrs", true );   //返回第一個匹配元素關聯的自定義數據緩存對象。如果沒有匹配元素則會返回null
        }
      }

      return data;

    } else if ( typeof key === "object" ) {         //如果key是一個對象,則為每個元素對象調用方法$.data(this,key)批量設置數據
      return this.each(function() {
        jQuery.data( this, key );
      });
    }

    parts = key.split(".");
    parts[1] = parts[1] ? "." + parts[1] : "";      //取出命名空間,比如$(this).data('a.b',123);則parts[1]是.b

    if ( value === undefined ) {                    //如果傳入的格式是.data(key),則認為是讀取單個數據
      data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]);     //觸發自定義事件getData,並把事件監聽函數的返回值賦值給變數data

      // Try to fetch any internally stored data first
      if ( data === undefined && this.length ) {          //如果事件監聽函數沒有返回值,才會嘗試從自定義數據緩存對象中讀取
        data = jQuery.data( this[0], key );
        data = dataAttr( this[0], key, data );
      }

      return data === undefined && parts[1] ?             //如果從getData()事件監聽函數或自定義數據緩存對象或HTML5屬性data-中取到了數據,則返回數據;如果沒有取到數據,但是指定了命名空間,則去掉命名空間再次嘗試讀取。
        this.data( parts[0] ) :
        data;

    } else {                                          //如果傳入了參數key和value,即參數格式是:.data(key,value),則為每個匹配元素設置任意類型的數據,並觸發自定義事件setData()和changeData()。
      return this.each(function() {
        var self = jQuery( this ),
          args = [ parts[0], value ];

        self.triggerHandler( "setData" + parts[1] + "!", args );        //觸發自定義事件setData,感嘆號表示只執行沒有命名控制項的事件監聽函數
        jQuery.data( this, key, value );                                //調用$.data()方法為任意匹配元素設置任意類型的數據
        self.triggerHandler( "changeData" + parts[1] + "!", args );     //觸發自定義事件changeData
      });
    }
  },
  /*...*/
})

設置數據緩存就是這樣的,理解了設置數據緩存,移除就很好理解了。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.本地Navicat for MySQL無法連接至伺服器(Centos 7 x86_64 bbr) 2.部署到伺服器上後(Centos 7 x86_64 bbr)表單提交亂碼問題 ...
  • 環境準備: master:192.168.0.106:3306slave:192.168.0.105:3306 主和從都必須配置有唯一的ID(server_id:建議ip最後一組+MySQL埠號,例如:1063306,1053306) 採用gtid模式複製,需要配置mysqld開啟以下兩個參數 操 ...
  • 此題有兩個解法: 我初步嘗試用以下 解決問題(要刪除的記錄 肯定大於相同內容的 ): 但是無法通過,究其原因是在 語句中, 與`DELETE`操作不能同時存在. 答案一 因此嘗試新的解法,直接使用刪除語句,結果如下所示: 此答案通過測試,但是效率較低. 答案二 後續思考中發現,可以使用臨時表解決 與 ...
  • 前言 Redis 沒有直接使用 C 語言傳統的字元串表示(以空字元結尾的字元數組,以下簡稱 C 字元串), 而是自己構建了一種名為簡單動態字元串(simple dynamic string,SDS)的抽象類型, 並將 SDS 用作 Redis 的預設字元串表示。 個人感覺SDS類似於Java的Arr ...
  • mysql 的邏輯架構分為三層: 最上層的服務大多數基於網路的客戶端、伺服器的工具或者服務都有類似的架構,比如連接處理,授權認證、安全等 第二層架構:mysql的核心服務功能都在這一層,包括查詢解析,分析,優化,緩存以及所有的內置函數,所有跨存儲引擎的功能都在這一層實現:存儲過程,觸發器、視圖 第三 ...
  • 因為公司基本都是用存儲過程所以本來寫的乾貨基本都是存儲過程的。 用以上語句來說一下例子: 查詢 一定要指定欄位就算你要查全部欄位也不要用*號來代替 ,以及 能用TOP儘量TOP 避免沒必要的鎖 必須加 WITH(NOLOCK) 避免產生沒有必要的鎖出來。 因為欄位多,數據多一個索引沒有走。 加了欄位 ...
  • 本文基於 Android 9.0 , 代碼倉庫地址 : "android_9.0.0_r45" 文中源碼鏈接: "SystemServer.java" "SystemServiceManager.java" "SystemService.java" 首先來回顧一下上篇文章 "Java 世界的盤古和女 ...
  • Android中線程按功能分的話,可以分為兩個,一個是主線程(UI線程),其他的都是子線程 主線程不能執行那些耗時過長的代碼或任務(執行耗時過長的代碼會出現應用未響應的提示),所以都是使用子線程來執行耗時過長的代碼,比如說下載文件等任務 一般情況,子線程中執行過長的代碼,都是需要進行更新UI操作。 ...
一周排行
    -Advertisement-
    Play Games
  • .Net8.0 Blazor Hybird 桌面端 (WPF/Winform) 實測可以完整運行在 win7sp1/win10/win11. 如果用其他工具打包,還可以運行在mac/linux下, 傳送門BlazorHybrid 發佈為無依賴包方式 安裝 WebView2Runtime 1.57 M ...
  • 目錄前言PostgreSql安裝測試額外Nuget安裝Person.cs模擬運行Navicate連postgresql解決方案Garnet為什麼要選擇Garnet而不是RedisRedis不再開源Windows版的Redis是由微軟維護的Windows Redis版本老舊,後續可能不再更新Garne ...
  • C#TMS系統代碼-聯表報表學習 領導被裁了之後很快就有人上任了,幾乎是無縫銜接,很難讓我不想到這早就決定好了。我的職責沒有任何變化。感受下來這個系統封裝程度很高,我只要會調用方法就行。這個系統交付之後不會有太多問題,更多應該是做小需求,有大的開發任務應該也是第二期的事,嗯?怎麼感覺我變成運維了?而 ...
  • 我在隨筆《EAV模型(實體-屬性-值)的設計和低代碼的處理方案(1)》中介紹了一些基本的EAV模型設計知識和基於Winform場景下低代碼(或者說無代碼)的一些實現思路,在本篇隨筆中,我們來分析一下這種針對通用業務,且只需定義就能構建業務模塊存儲和界面的解決方案,其中的數據查詢處理的操作。 ...
  • 對某個遠程伺服器啟用和設置NTP服務(Windows系統) 打開註冊表 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NtpServer 將 Enabled 的值設置為 1,這將啟用NTP伺服器功 ...
  • title: Django信號與擴展:深入理解與實踐 date: 2024/5/15 22:40:52 updated: 2024/5/15 22:40:52 categories: 後端開發 tags: Django 信號 松耦合 觀察者 擴展 安全 性能 第一部分:Django信號基礎 Djan ...
  • 使用xadmin2遇到的問題&解決 環境配置: 使用的模塊版本: 關聯的包 Django 3.2.15 mysqlclient 2.2.4 xadmin 2.0.1 django-crispy-forms >= 1.6.0 django-import-export >= 0.5.1 django-r ...
  • 今天我打算整點兒不一樣的內容,通過之前學習的TransformerMap和LazyMap鏈,想搞點不一樣的,所以我關註了另外一條鏈DefaultedMap鏈,主要調用鏈為: 調用鏈詳細描述: ObjectInputStream.readObject() DefaultedMap.readObject ...
  • 後端應用級開發者該如何擁抱 AI GC?就是在這樣的一個大的浪潮下,我們的傳統的應用級開發者。我們該如何選擇職業或者是如何去快速轉型,跟上這樣的一個行業的一個浪潮? 0 AI金字塔模型 越往上它的整個難度就是職業機會也好,或者說是整個的這個運作也好,它的難度會越大,然後越往下機會就會越多,所以這是一 ...
  • @Autowired是Spring框架提供的註解,@Resource是Java EE 5規範提供的註解。 @Autowired預設按照類型自動裝配,而@Resource預設按照名稱自動裝配。 @Autowired支持@Qualifier註解來指定裝配哪一個具有相同類型的bean,而@Resourc... ...