Home  >  Article  >  Web Front-end  >  Usage of data cache $.data in jQuery and complete source code analysis_jquery

Usage of data cache $.data in jQuery and complete source code analysis_jquery

WBOY
WBOYOriginal
2016-05-16 15:02:511734browse

一、实现原理:

对于DOM元素,通过分配一个唯一的关联id把DOM元素和该DOM元素的数据缓存对象关联起来,关联id被附加到以jQuery.expando的值命名的属性上,数据存储在全局缓存对象jQuery.cache中。在读取、设置、移除数据时,将通过关联id从全局缓存对象jQuery.cache中找到关联的数据缓存对象,然后在数据缓存对象上执行读取、设置、移除操作。

对于Javascript对象,数据则直接存储在该Javascript对象的属性jQuery.expando上。在读取、设置、移除数据时,实际上是对Javascript对象的数据缓存对象执行读取、设置、移除操作。

为了避免jQuery内部使用的数据和用户自定义的数据发生冲突,数据缓存模块把内部数据存储在数据缓存对象上,把自定义数据存储在数据缓存对象的属性data上。

二、总体结构:

// 数据缓存 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){}
});

三、$.data(elem, name, data), $.data(elem, name)
$.data(elem, name, data)的使用方法:
如果传入参数name, data, 则设置任意类型的数据

<!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, 则读取并返回指定名称的数据

<!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>&#63;</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 &#63; 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 &#63; 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&#63; 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 );
 },
});

四、.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] &#63; "." + 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] &#63;
    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" &#63; true :
    data === "false" &#63; false :
    data === "null" &#63; null :
    jQuery.isNumeric( data ) &#63; parseFloat( data ) :
     rbrace.test( data ) &#63; 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;
}


五、$.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 &#63; jQuery.cache : elem,

   // See jQuery.data for more information
   id = isNode &#63; 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 &#63; 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 &#63; 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;
}


六、$.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 &#63; jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
   return !!elem && !isEmptyDataObject( elem );
   // 如果关联的数据缓存对象存在,并且含有数据,则返回true, 否则返回false。 这里用两个逻辑非运算符! 把变量elem转换为布尔值
 }
});

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn