您的位置:首页 > Web前端 > JQuery

jQuery源码之$.data()数据缓存

2015-09-10 22:18 591 查看

attr()与prop()

$.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原型上的方法

Data.prototype={
         key:function(){//.....},  //用于获取键值
         set:function(){//......},  //写入值
         get:function(){//......},   //读值
         access:function(){//......},  //get set的快捷方式
         remove:function(){//......},  //删除cache中key对应的值
         hasData:function(){//......},  //判断是否有缓存
         discard:function(){}  //删除cache中的值包括key
   
 }


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; //返回键值
	},

set()解析:

</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()方法:

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()方法:

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()方法
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():

hasData: function( owner ) {  //如果存在返回true
		return !jQuery.isEmptyObject(
			this.cache[ owner[ this.expando ] ] || {}
		);
	},

discard():

discard: function( owner ) {
		if ( owner[ this.expando ] ) {
			delete this.cache[ owner[ this.expando ] ]; //删除键值对应的对象
		}
	}


jQ data()工具方法

源码
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


jq的data()实例方法

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 );
		});
	}
});
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: