$.data()或者$(selector).data()可以向元素上添加数据,类似于attr()或者prop()方法。但是attr()或者prop()方法容易形成循环引用,造成内存泄露。例如:
require(["math","jquery"],function(math,$){ var obj={ dom:$(".test5")[0], name:"div" }; $(".test5").attr("name",obj); });
模块中的代码就形成了一个循环引用。
data()数据缓存会在元素上生成一个属性,该属性是
this.expando = jQuery.expando + Data.uid++;
是一个唯一的,属性的值为 数值:1,2,3...,jquery内部会建立一个对象cache用于缓存数据。
例如:$("#div1").data("name","myDiv"),这时,cache[1]="myDIv"
<div id="div1" jQuery2140289804814383387571="1">
由此可见,使用data()数据缓存,不会造成循环引用。
function Data() { // Support: Android<4, // Old WebKit does not have Object.preventExtensions/freeze method, // return new empty object instead with no [[set]] accessor Object.defineProperty( this.cache = {}, 0, { get: function() { return {}; } }); this.expando = jQuery.expando + Data.uid++; } Data.uid = 1; Data.accepts = jQuery.acceptData;
Data()构造函数,函数内部定义了cache属性,cache属性用于缓存数据。Object.defineProperty()是ES5中定义的用于生成或者对象属性,这里get方法用于读取属性值,没有set方法说明该属性是只读的。jQuery.expando是jquery版本号加一个随机数,当调用data()时,会向元素添加属性expando。而Data.uid
作为键值来确定是cache中的哪个值。下面看下acceptData()
jQuery.acceptData = function( owner ) { // Accepts only: // - Node // - Node.ELEMENT_NODE // - Node.DOCUMENT_NODE // - Object // - Any /* jshint -W018 */ return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); };该函数只有在DOM元素、document和对象是返回true;
Data.prototype={ key:function(){//.....}, //用于获取键值 set:function(){//......}, //写入值 get:function(){//......}, //读值 access:function(){//......}, //get set的快捷方式 remove:function(){//......}, //删除cache中key对应的值 hasData:function(){//......}, //判断是否有缓存 discard:function(){} //删除cache中的值包括key }
key: function( owner ) { // We can accept data for non-element nodes in modern browsers, // but we should not, see #8335. // Always return the key for a frozen object. if ( !Data.accepts( owner ) ) {//如果不是对象、DOM元素或者document对象,就返回0 return 0; } var descriptor = {}, // Check if the owner object already has a cache key unlock = owner[ this.expando ]; //键值 // If not, create one if ( !unlock ) { //键值不存在,就创建一个 unlock = Data.uid++; // Secure it in a non-enumerable, non-writable property try { descriptor[ this.expando ] = { value: unlock }; //将对象添加到元素上,同时保证对象不能被修改 Object.defineProperties( owner, descriptor ); // Support: Android<4 // Fallback to a less secure definition } catch ( e ) { //如果不支持就使用extend descriptor[ this.expando ] = unlock; jQuery.extend( owner, descriptor ); } } // Ensure the cache object if ( !this.cache[ unlock ] ) { //确保 this.cache[ unlock ] = {}; } return unlock; //返回键值 },
</pre><pre name="code" class="javascript">set: function( owner, data, value ) { var prop, // There may be an unlock assigned to this node, // if there is no entry for this "owner", create one inline // and set the unlock as though an owner entry had always existed unlock = this.key( owner ),//获取键值 cache = this.cache[ unlock ]; //然后cache引用键值对应的对象 // Handle: [ owner, key, value ] args if ( typeof data === "string" ) { //单个数据赋值操作 cache[ data ] = value; // Handle: [ owner, { properties } ] args } else { // // Fresh assignments by object are shallow copied if ( jQuery.isEmptyObject( cache ) ) { //如果cache是空对象,直接利用jq的extend方法扩展 jQuery.extend( this.cache[ unlock ], data ); // Otherwise, copy the properties one-by-one to the cache object } else { //不是空对象想,则利用for in循环添加。 for ( prop in data ) { cache[ prop ] = data[ prop ]; } } } return cache; },
get: function( owner, key ) { // Either a valid cache is found, or will be created. // New caches will be created and the unlock returned, // allowing direct access to the newly created // empty data object. A valid owner object must be provided. var cache = this.cache[ this.key( owner ) ]; //根据键值来获取值 return key === undefined ? //如果没有入参,则返回整个缓存 cache : cache[ key ]; },
access: function( owner, key, value ) { var stored; // In cases where either: // // 1. No key was specified // 2. A string key was specified, but no value provided // // Take the "read" path and allow the get method to determine // which value to return, respectively either: // // 1. The entire cache object // 2. The data stored at the key // if ( key === undefined || //如果键值不存在或者键值存在但是值不存在的情况下,就调用get方法 ((key && typeof key === "string") && value === undefined) ) { stored = this.get( owner, key ); return stored !== undefined ? stored : this.get( owner, jQuery.camelCase(key) ); } // [*]When the key is not a string, or both a key and value // are specified, set or extend (existing objects) with either: // // 1. An object of properties // 2. A key and value // this.set( owner, key, value ); //设值 // Since the "set" path can have two possible entry points // return the expected data based on which path was taken[*] return value !== undefined ? value : key; },
remove: function( owner, key ) { var i, name, camel, unlock = this.key( owner ), cache = this.cache[ unlock ]; if ( key === undefined ) { //如果没有传入键值,则删掉所有值 this.cache[ unlock ] = {}; } else { // Support array or space separated string of keys if ( jQuery.isArray( key ) ) { //如果传入的是数组(可以删除多个值) // If "name" is an array of keys... // When data is initially created, via ("key", "val") signature, // keys will be converted to camelCase. // Since there is no way to tell _how_ a key was added, remove // both plain key and camelCase key. #12786 // This will only penalize the array argument path. name = key.concat( key.map( jQuery.camelCase ) ); 将key值转换为驼峰写法,加到数组中 } else { camel = jQuery.camelCase( key ); //删除单个 // Try the string as a key before any manipulation if ( key in cache ) { name = [ key, camel ]; } else { //入参不再cache中,则看看驼峰写法有没有,如果还没有,去完空格看看是否匹配,如果还没有,返回空 // If a key with the spaces exists, use it. // Otherwise, create an array by matching non-whitespace name = camel; name = name in cache ? [ name ] : ( name.match( rnotwhite ) || [] ); } } i = name.length; while ( i-- ) { delete cache[ name[ i ] ]; //删除 } } },
hasData: function( owner ) { //如果存在返回true return !jQuery.isEmptyObject( this.cache[ owner[ this.expando ] ] || {} ); },
discard: function( owner ) { if ( owner[ this.expando ] ) { delete this.cache[ owner[ this.expando ] ]; //删除键值对应的对象 } }
jQuery.extend({ hasData: function( elem ) { return data_user.hasData( elem ) || data_priv.hasData( elem ); //调用公开的Data实例或者私有实例上的hasData()方法 }, data: function( elem, name, data ) { return data_user.access( elem, name, data ); //调用Data实例上的access()方法 }, removeData: function( elem, name ) { //删除元素上的数据缓存 data_user.remove( elem, name ); }, // TODO: Now that all calls to _data and _removeData have been replaced // with direct calls to data_priv methods, these can be deprecated. _data: function( elem, name, data ) { //内部使用的的方法 return data_priv.access( elem, name, data ); }, _removeData: function( elem, name ) { //jq内部使用的私有删除方法 data_priv.remove( elem, name ); } });
$.data($(".test5")[0],"name","vuturn"); console.log($.data($(".test5")[0],"name")); //vuturn $.removeData($(".test5")[0],"name"); console.log($.data($(".test5")[0],"name")); //undefined
jQuery.fn.extend({ data: function( key, value ) { var i, name, data, elem = this[ 0 ], attrs = elem && elem.attributes; // Gets all values if ( key === undefined ) { //如果键值不存在 if ( this.length ) { //. 如果选择器的长度大于1 data = data_user.get( elem ); //利用公开的data实例的get方法获取。 if ( elem.nodeType === 1 && !data_priv.get( elem, "hasDataAttrs" ) ) { //如果是DOM元素,且没有hasDataAttr i = attrs.length; //DOM元素属性的个数 while ( i-- ) { // Support: IE11+ // The attrs elements can be null (#14894) if ( attrs[ i ] ) { name = attrs[ i ].name; if ( name.indexOf( "data-" ) === 0 ) { name = jQuery.camelCase( name.slice(5) ); //遍历看看是否有html5自定义属性 dataAttr( elem, name, data[ name ] ); } } } data_priv.set( elem, "hasDataAttrs", true ); } } return data; } // Sets multiple values if ( typeof key === "object" ) { //设多个值 return this.each(function() { data_user.set( this, key ); }); } return access( this, function( value ) { //调用access设值 var data, camelKey = jQuery.camelCase( key ); // The calling jQuery object (element matches) is not empty // (and therefore has an element appears at this[ 0 ]) and the // `value` parameter was not undefined. An empty jQuery object // will result in `undefined` for elem = this[ 0 ] which will // throw an exception if an attempt to read a data cache is made. if ( elem && value === undefined ) {//如果value不存在,取值 // Attempt to get data from the cache // with the key as-is data = data_user.get( elem, key ); if ( data !== undefined ) { return data; } // Attempt to get data from the cache // with the key camelized data = data_user.get( elem, camelKey ); //这时是转换为驼峰写法,继续查找 if ( data !== undefined ) { return data; } // Attempt to "discover" the data in // HTML5 custom data-* attrs data = dataAttr( elem, camelKey, undefined ); //如果还找不到,则把自定义属性返回 if ( data !== undefined ) { return data; } // We tried really hard, but the data doesn't exist. return; } // Set the data... this.each(function() { // First, attempt to store a copy or reference of any // data that might've been store with a camelCased key. var data = data_user.get( this, camelKey ); // For HTML5 data-* attribute interop, we have to // store property names with dashes in a camelCase form. // This might not apply to all properties...* data_user.set( this, camelKey, value ); // *... In the case of properties that might _actually_ // have dashes, we need to also store a copy of that // unchanged property. if ( key.indexOf("-") !== -1 && data !== undefined ) { data_user.set( this, key, value ); } }); }, null, value, arguments.length > 1, null, true ); }, removeData: function( key ) { return this.each(function() { data_user.remove( this, key ); }); } });