jQuery數據緩存$.data 的使用以及源碼解析

  1. 實現原理
  2. 整體結構
  3. $.data(elem, name, data), $.data(elem, name)
  4. .data(key, value), .data(key)
  5. $.removeData(elem, name),.removeData(key)
  6. $.hasData(elem)

1、實現原理:

對於DOM元素,經過分配一個惟一的關聯id把DOM元素和該DOM元素的數據緩存對象關聯起來,關聯id被附加到以jQuery.expando的值命名的屬性上,數據存儲在全局緩存對象jQuery.cache中。在讀取、設置、移除數據時,將經過關聯id從全局緩存對象jQuery.cache中找到關聯的數據緩存對象,而後在數據緩存對象上執行讀取、設置、移除操做。javascript

對於Javascript對象,數據則直接存儲在該Javascript對象的屬性jQuery.expando上。在讀取、設置、移除數據時,其實是對Javascript對象的數據緩存對象執行讀取、設置、移除操做。html

爲了不jQuery內部使用的數據和用戶自定義的數據發生衝突,數據緩存模塊把內部數據存儲在數據緩存對象上,把自定義數據存儲在數據緩存對象的屬性data上。java

2、整體結構:

// 數據緩存 Data
jQuery.extend({
     // 全局緩存對象
     cache: {},
     // 惟一 id種子
     uuid:0,
     // 頁面中每一個jQuery副本的惟一標識
     expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ),
     // 是否有關聯的數據
     hasData: function(){},
     // 設置、讀取自定數據或內部數據
     data: function(elem, name, data, pvt) {},
     // 移除自定義數據或內部數據
     removeData: function(elem, name, pvt) {},
     // 設置、讀取內部數據
     _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){}
});

3、$.data(elem, name, data), $.data(elem, name)

$.data(elem, name, data)的使用方法:

若是傳入參數name, data, 則設置任意類型的數據node

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>jQuery.data demo</title>
  <style>
  div {
    color: blue;
  }
  span {
    color: red;
  }
  </style>
  <script src="//code.jquery.com/jquery-1.10.2.js"></script>
</head>
<body>

<div>
  The values stored were
  <span></span>
  and
  <span></span>
</div>

<script>
var div = $( "div" )[ 0 ];
jQuery.data( div, "test", {
  first: 16,
  last: "pizza!"
});
$( "span:first" ).text( jQuery.data( div, "test" ).first );
$( "span:last" ).text( jQuery.data( div, "test" ).last );
</script>

</body>
</html>

$.data(elem, name)的使用方法:

若是傳入key, 未傳入參數data, 則讀取並返回指定名稱的數據jquery

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>jQuery.data demo</title>
  <style>
  div {
    margin: 5px;
    background: yellow;
  }
  button {
    margin: 5px;
    font-size: 14px;
  }
  p {
    margin: 5px;
    color: blue;
  }
  span {
    color: red;
  }
  </style>
  <script src="//code.jquery.com/jquery-1.10.2.js"></script>
</head>
<body>

<div>A div</div>
<button>Get "blah" from the div</button>
<button>Set "blah" to "hello"</button>
<button>Set "blah" to 86</button>
<button>Remove "blah" from the div</button>
<p>The "blah" value of this div is <span>?</span></p>

<script>
$( "button" ).click( function() {
  var value,
    div = $( "div" )[ 0 ];
  switch ( $( "button" ).index( this ) ) {
  case 0 :
    value = jQuery.data( div, "blah" );
    break;
  case 1 :
    jQuery.data( div, "blah", "hello" );
    value = "Stored!";
    break;
  case 2 :
    jQuery.data( div, "blah", 86 );
    value = "Stored!";
    break;
  case 3 :
    jQuery.removeData( div, "blah" );
    value = "Removed!";
    break;
  }
  $( "span" ).text( "" + value );
});
</script>

</body>
</html>

$.data(elem, name, data), $.data(elem, name) 源碼解析:

jQuery.extend({
  // 1. 定義jQuery.data(elem, name, data, pvt)
  data: function( elem, name, data, pvt /* Internal Use Only */ ) {
    // 2. 檢查是否能夠設置數據
    if ( !jQuery.acceptData( elem ) ) {
      return; // 若是參數elem不支持設置數據,則當即返回
    }

    // 3 定義局部變量
    var privateCache, 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, // 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元素,爲了不javascript和DOM元素之間循環引用致使的瀏覽器(IE6/7)垃圾回收機制不起做用,要把數據存儲在全局緩存對象jQuery.cache中;對於javascript對象,來及回收機制可以自動發生,不會有內存泄露的問題,所以數據能夠查收存儲在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
    // 4. 若是是讀取數據,但沒有數據,則返回
    if ( (!id || !cache[id] || (!isEvents && !pvt && !cache[id].data)) && getByName && data === undefined ) {
      return;
      // getByName && data === undefined 若是name是字符串,data是undefined, 說明是在讀取數據
      // !id || !cache[id] || (!isEvents && !pvt && !cache[id].data 若是關聯id不存在,說明沒有數據;若是cache[id]不存在,也說明沒有數據;若是是讀取自動以數據,但cache[id].data不存在,說明沒有自定義數據
    }

    // 5. 若是關聯id不存在,則分配一個
    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 = ++jQuery.uuid; // 對於DOM元素,jQuery.uuid會自動加1,並附加到DOM元素上
      } else {
        id = internalKey; // 對於javascript對象,關聯id就是jQuery.expando
      }
    }

    // 6. 若是數據緩存對象不存在,則初始化爲空對象{}
    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; // 對於javascript對象,設置方法toJSON爲空函數,以免在執行JSON.stringify()時暴露緩存數據。若是一個對象定義了方法toJSON(),JSON.stringify()在序列化該對象時會調用這個方法來生成該對象的JSON元素
      }
    }

    // An object can be passed to jQuery.data instead of a key/value pair; this gets
    // shallow copied over onto the existing cache
    // 7. 若是參數name是對象或函數,則批量設置數據
    if ( typeof name === "object" || typeof name === "function" ) {
      if ( pvt ) {
        cache[ id ] = jQuery.extend( cache[ id ], name ); // 對於內部數據,把參數name中的屬性合併到cache[id]中
      } else {
        cache[ id ].data = jQuery.extend( cache[ id ].data, name ); // 對於自定義數據,把參數name中的屬性合併到cache[id].data中
      }
    }

    // 8. 若是參數data不是undefined, 則設置單個數據
    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 ) {
      if ( !thisCache.data ) {
        thisCache.data = {};
      }

      thisCache = thisCache.data;
    }

    if ( data !== undefined ) {
      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.
    // 9. 特殊處理events 
    if ( isEvents && !thisCache[ name ] ) { // 若是參數name是字符串"events",而且未設置過自定義數據"events",則返回事件婚車對象,在其中存儲了事件監聽函數。
      return privateCache.events;
    }

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

      // 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 { // 11. 若是未傳入參數name,data,則返回數據緩存對象
      ret = thisCache;
    }

    return ret;
  },

  // For internal use only.
  _data: function( elem, name, data ) {
    return jQuery.data( elem, name, data, true );
  },
});

4、.data(key, value), .data(key)

使用方法:

$( "body" ).data( "foo", 52 ); // 傳入key, value
    $( "body" ).data( "bar", { myType: "test", count: 40 } ); // 傳入key, value
    $( "body" ).data( { baz: [ 1, 2, 3 ] } ); // 傳入key, value
    $( "body" ).data( "foo" ); // 52 // 傳入key
    $( "body" ).data(); // 未傳入參數

HTML5 data attriubutes:瀏覽器

<div data-role="page" data-last-value="43" data-hidden="true" data-options='{"name":"John"}'></div>

    $( "div" ).data( "role" ) === "page";
    $( "div" ).data( "lastValue" ) === 43;
    $( "div" ).data( "hidden" ) === true;
    $( "div" ).data( "options" ).name === "John";

.data(key, value), .data(key) 源碼解析

jQuery.fn.extend({
  // 1. 定義.data(key, value)
  data: function( key, value ) {
    var parts, attr, name,
      data = null;

    // 2. 未傳入參數的狀況
    if ( typeof key === "undefined" ) {
      if ( this.length ) { // 若是參數key是undefined, 即參數格式是.data(), 則調用方法jQuery.data(elem, name, data, pvt)獲取第一個匹配元素關聯的自定義數據緩存對象,並返回。
        data = jQuery.data( this[0] );

        if ( this[0].nodeType === 1 && !jQuery._data( this[0], "parsedAttrs" ) ) {
          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 );
        }
      }

      return data;
    // 3. 參數key 是對象的狀況,即參數格式是.data(key),則遍歷匹配元素集合,爲每一個匹配元素調用方法jQuery.data(elem, name, data,pvt)批量設置數據
    } else if ( typeof key === "object" ) {
      return this.each(function() {
        jQuery.data( this, key );
      });
    }
    // 4. 只傳入參數key的狀況  若是隻傳入參數key, 即參數格式是.data(key),則返回第一個匹配元素的指定名稱數據
    parts = key.split(".");
    parts[1] = parts[1] ? "." + parts[1] : "";

    if ( value === undefined ) {
      data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]);

      // 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] ?
        this.data( parts[0] ) :
        data;

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

        self.triggerHandler( "setData" + parts[1] + "!", args );
        jQuery.data( this, key, value );
        self.triggerHandler( "changeData" + parts[1] + "!", args );
      });
    }
  },

  removeData: function( key ) {
    return this.each(function() {
      jQuery.removeData( this, key );
    });
  }
});

// 6. 函數dataAttr(elem, key, data)解析HTML5屬性data-
function dataAttr( elem, key, data ) {
  // If nothing was found internally, try to fetch any
  // data from the HTML5 data-* attribute
  // 只有參數data爲undefined時,纔會解析HTML5屬性data-
  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 :
        jQuery.isNumeric( data ) ? parseFloat( 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;
}

5、$.removeData(elem, name),.removeData(key)

使用方法:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>jQuery.removeData demo</title>
  <style>
  div {
    margin: 2px;
    color: blue;
  }
  span {
    color: red;
  }
  </style>
  <script src="//code.jquery.com/jquery-1.10.2.js"></script>
</head>
<body>

<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" ) ); //undefined
jQuery.data( div, "test1", "VALUE-1" );
jQuery.data( div, "test2", "VALUE-2" );
$( "span:eq(1)" ).text( "" + jQuery.data( div, "test1" ) ); // VALUE-1
jQuery.removeData( div, "test1" );
$( "span:eq(2)" ).text( "" + jQuery.data( div, "test1" ) ); // undefined
$( "span:eq(3)" ).text( "" + jQuery.data( div, "test2" ) ); // value2
</script>

</body>
</html>


<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>removeData demo</title>
  <style>
  div {
    margin: 2px;
    color: blue;
  }
  span {
    color: red;
  }
  </style>
  <script src="//code.jquery.com/jquery-1.10.2.js"></script>
</head>
<body>

<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>
$( "span:eq(0)" ).text( "" + $( "div" ).data( "test1" ) ); // undefined
$( "div" ).data( "test1", "VALUE-1" );
$( "div" ).data( "test2", "VALUE-2" );
$( "span:eq(1)" ).text( "" + $( "div").data( "test1" ) ); // VALUE-1
$( "div" ).removeData( "test1" );
$( "span:eq(2)" ).text( "" + $( "div" ).data( "test1" ) ); // undefined
$( "span:eq(3)" ).text( "" + $( "div" ).data( "test2" ) ); // VALUE-2
</script>

</body>
</html>

$.removeData(elem, name),.removeData(key) 源碼解析:

$.extend({
    // jQuery.removeData(elem,name,pvt)用於移除經過jQuery.data()設置的數據
  removeData: function( elem, name, pvt /* Internal Use Only */ ) {
    if ( !jQuery.acceptData( elem ) ) {
      return;
    }

    var thisCache, i, l,

      // Reference to internal data cache key
      internalKey = jQuery.expando,

      isNode = elem.nodeType,

      // See jQuery.data for more information
      cache = isNode ? jQuery.cache : elem,

      // See jQuery.data for more information
      id = isNode ? elem[ internalKey ] : internalKey;

    // If there is already no cache entry for this object, there is no
    // purpose in continuing
    if ( !cache[ id ] ) {
      return;
    }

        // 若是傳入參數name, 則移除一個或多個數據
    if ( name ) {

      thisCache = pvt ? cache[ id ] : cache[ id ].data;

      if ( thisCache ) { // 只有數據緩存對象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( " " );
            }
          }
        }

        // 遍歷參數name中的數據名,用運算符delete逐個從數據緩存對象thisCache中移除
        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
    // 刪除自定義數據緩存對象cache[id].data
    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;
      }
    }

    // Browsers that fail expando deletion also refuse to delete expandos on
    // the window, but it will allow it on all other JS objects; other browsers
    // don't care
    // Ensure that `cache` is not a window object #10080
    // 刪除數據緩存對象cache[id]
    if ( jQuery.support.deleteExpando || !cache.setInterval ) {
      delete cache[ id ];
    } else {
      cache[ id ] = null;
    }

    // We destroyed the cache and need to eliminate the expando on the node to avoid
    // false lookups in the cache for entries that no longer exist
    // 刪除DOM元素上擴展的jQuery.expando屬性
    if ( isNode ) {
      // 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 ( jQuery.support.deleteExpando ) {
        delete elem[ internalKey ];
      } else if ( elem.removeAttribute ) {
        elem.removeAttribute( internalKey );
      } else {
        elem[ internalKey ] = null;
      }
    }
  }
});

jQuery.fn.extend({
    removeData: function( key ) {
      return this.each(function() {
        jQuery.removeData( this, key );
      });
    }
});

// checks a cache object for emptiness
function isEmptyDataObject( obj ) {
  for ( var 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;
}

6、$.hasData(elem)

使用方法:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>jQuery.hasData demo</title>
  <script src="//code.jquery.com/jquery-1.10.2.js"></script>
</head>
<body>

<p>Results: </p>

<script>
var $p = jQuery( "p" ), p = $p[ 0 ];
$p.append( jQuery.hasData( p ) + " " ); // false

$.data( p, "testing", 123 );
$p.append( jQuery.hasData( p ) + " " ); // true

$.removeData( p, "testing" );
$p.append( jQuery.hasData( p ) + " " ); // false

$p.on( "click", function() {} );
$p.append( jQuery.hasData( p ) + " " ); // true

$p.off( "click" );
$p.append( jQuery.hasData( p ) + " " ); // false
</script>

</body>
</html>

$.hasData(elem) 源碼解析:

$.extend({
    hasData: function( elem ) {
      elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
      return !!elem && !isEmptyDataObject( elem );
      // 若是關聯的數據緩存對象存在,而且含有數據,則返回true, 不然返回false。 這裏用兩個邏輯非運算符! 把變量elem轉換爲布爾值
  }
});
相關文章
相關標籤/搜索