jQuery 源码 data 数据缓存


data数据缓存 3308


先做个区分:
下面这三种方式,都可以弹出hello。
attr使用方法。
	$('#content-box').attr('name', 'hello');
	alert($('#content-box').attr('name'));		//hello

document.getElementById('#content-box').setAttribute('name', 'hello');
alert(document.getElementById('#content-box').getAttribute('name'));


prop使用方法.
	$('#content-box').prop('name', 'hello');
	alert($('#content-box').prop('name'));		//hello

document.getElementById('#content-box').name = 'hello';
alert(document.getElementById('#content-box').name);
[] 和 . 是一样的。
document.getElementById('#content-box')['name'] = 'hello';
alert(document.getElementById('#content-box')['name']);

上面的两种方法,不适合做大量数据,适合做简单数据或者元素本身的属性。设定一个class,id等。
但是要加载很多数据的时候,就用data挂载大量的数据。

data使用方法.
	$('#content-box').data('name', 'hello');
	alert($('#content-box').data('name'));		//hello

讨论一下JS内存泄漏的问题。
	变量不用的时候,浏览器有自动的垃圾回收机制,去销毁掉。这个变量就消失了。内存释放。
JS有几种方式可以引起内存泄漏的。
	其中一种就是dom元素与对象之间互相引用。大部分浏览器就会出现内存泄漏。

DOM是如何与对象间产生内存泄漏的。
举个例子:
		
	html
	<div id='test' class='content-box'></div>

	JS
	var oDiv = document.getElementById('test');
	var obj = {};

	oDiv.name = obj;
	obj.age = oDiv;

解释一下:oDiv是一个DOM的对象,而obj又是一个JS的对象。
首先,给oDiv添加了一个name属性,赋值为obj,
然后,又用obj去引用oDiv。就出现了内存泄漏。

// 	这里选择器先选出了一个DMO对象,然后可能会对其中的属性,进行加载对象操作。
	$('#content-box').attr('name', obj);
//	有可能未来的某一行代码,就出现了obj去引用DOM的可能,那就出现了内存泄漏的这个坑。

解决问题的方式,就是用data。
	就不用担心内存泄漏的问题,data会防止DOM和对象之间的相互引用,防止内存泄漏。
	$('#content-box').data('name', obj);
	


/*****************************************************/

设计思路:
	
	data设计了一个中介,将DOM与数据(对象),间接的联系在一起。
通过cache = {}, 来进行连接,做了一个映射。
比如:
$('#box1').attr('XXX', obj1);
$('#box2').attr('YYY', obj2);
$('#box3').attr('ZZZ', obj3);

由于选择器一定是有先后顺序的,所以,为box1的XXX属性设定一个1.
box2的YYY属性设定一个2,box3的ZZZ属性设定一个3。都存入cache的对象中。

此时:cache = {}----->>>>

				转变为----->>>>>

						cache = {
							1: {
								XXX : obj1
							},
							2: {
								YYY : obj2
							},
							3: {
								ZZZ : obj3
							}
						}

而HTML中的属性----->>>>
		<div id="box1"></div>
		<div id="box2"></div>
		<div id="box3"></div>						
					转变为----->>>>>
							<div id="box1" XXX='1'></div>
							<div id="box2" YYY='2'></div>
							<div id="box3" ZZZ='3'></div>

								这样, XXX可以通过1,来找到cache对象中的响应的属性值。
									[XXX ---> 1]
									[YYY ---> 2]
									[ZZZ ---> 3]

利用这种方式,可以防止内存泄漏。这回将数据存入Json中,也就是cache中。
跟DOM就没关系了。因为dom中存放的是一个字符串,也就是键值对的形式。反正不是对象。
通过cache建立了链接,给dom上面挂载数据。

jQuery的小习惯。
	$('div').html('hello');		//		可以设定一堆
	但是,
	alert($('div').html());		//   	只返回一个。


/*****************************************************/

结构:
	Data.prototype = {
		key 			// 	分配映射的。类似有搞定键值对。
		set   			//	设定	
		get 			//	获取
		access 			//	get/set的集合方法。2个参数,用get,三个参数set
		remove          //	删除cache中元素
		hasData 		// 	判断是否存在
		discard 		//	删除cache中苏醒
	}

/*****************************************************/
结构:
	jQuery.extend({
		acceptData	    				//	可以接受元素的范围   
		hasData 		Data->hasData	//	判断是否有这个数据,有就返回真,没有返回假。
		data 			Data->access	//	绑定数据,获取,设定参数。
		removeData 		Data->remove	//  删除数据
		_data 							//	这是私有方法。
		_removeData						//	这是私有方法。内部使用
	});
这里的Data是,上面的对象。

使用例子:
extend的方法,既可以给原生的js用,也可以给jQuery对象用。
	
	$.data(document.body, 'age', 22);
	alert($.data(document.body, 'age'));		//22
	alert($.hasData(document.body, 'age'));		// true

	$.removeData(document.body, 'age');
	alert($.data(document.body, 'age'));		//undefined
	alert($.hasData(document.body, 'age'));		// false


	$.data(document.body, {'hg', 'wj'});	//这样也可以,里面可以处理json数据格式
	
/*****************************************************/

结构:
	jQuery.fn.extend({
		data
		removeData
	})

使用例子:
	$('#test').data('name', 'hgwj');	// 绑定数据
	alert($('#test').data('name'));		// hgwj

	$('#test').removeData('name');		// 删除数据
	alert($('#test').data('name'));		// undefined


/***********************************************************************************/

阅读源码:
	
var data_user, data_priv,
	rbrace = /(?:{[sS]*}|[[sS]*])$/,
	rmultiDash = /([A-Z])/g;

function Data() {
	// Support: Android < 4,
	// Old WebKit does not have Object.preventExtensions/freeze method,
	// return new empty object instead with no [[set]] accessor
	// 	英文介绍,老式的webkit不支持这个方法。所以选择用这个defineProperty来代替。
	//	preventExtensions 和 freeze 作用就是防止对象被修改。
	//  Android < 4 不支持。ES5的方法defineProperty

	Object.defineProperty( this.cache = {}, 0, {
		get: function() {
			return {};
		}
	});

	//	返回一个jQuery的版本 +随机数 +  随机数的组合。
	//	 重复的概率很低,
	this.expando = jQuery.expando + Math.random();		

	console.log(this.expando);		//jQuery203083968277710742660.6855781189593687
	console.log(this);
	// this 对应下面一拖数据。 有一些属性和方法,已经键值对说对应的值。	
/*	
	Data {cache: Object, expando: "jQuery203077120183012448250.8953614917118102"}
	cache: Object0: (...)get 0: ()arguments: nullcaller: nulllength: 0name: ""prototype: Object.defineProperty.get__proto__: ()<function scope>1: Objectage: 22__proto__: Object__proto__: Object__defineGetter__: __defineGetter__()__defineSetter__: __defineSetter__()__lookupGetter__: __lookupGetter__()__lookupSetter__: __lookupSetter__()constructor: Object()hasOwnProperty: hasOwnProperty()isPrototypeOf: isPrototypeOf()propertyIsEnumerable: propertyIsEnumerable()toLocaleString: toLocaleString()toString: toString()valueOf: valueOf()get __proto__: get __proto__()set __proto__: set __proto__()expando: "jQuery203077120183012448250.8953614917118102"__proto__: Object
	jquery-2.0.3.js:3323 Data {cache: Object, expando: "jQuery203077120183012448250.46686112554743886"}cache: Objectexpando: "jQuery203077120183012448250.46686112554743886"__proto__: Object
*/
}

Data.uid = 1;

Data.accepts = function( owner ) {
	// Accepts only:
	//  - Node
	//    - Node.ELEMENT_NODE
	//    - Node.DOCUMENT_NODE
	//  - Object
	//    - Any
	//	这里是jQuery中DOM选择器选出来的是DOM节点,如果是1 是节点, 9 是文档,
	return owner.nodeType ?
		owner.nodeType === 1 || owner.nodeType === 9 : true;
};

Data.prototype = {
	key: function( owner ) {
		// 判断有没有元素,没有就返回0.
		if ( !Data.accepts( owner ) ) {
			return 0;
		}

		var descriptor = {},
			// Check if the owner object already has a cache key
			//	看是否是第一次进来,如果是第一次,就证明没有分配过 this.expando
			// 	unlock就是undefiend,如果分配过,证明这个元素已经不是第一次分配数据了。
			unlock = owner[ this.expando ];	

		// If not, create one
		// 没有创建过 this.expando 属性,就创建,并且添加入节点中。
		if ( !unlock ) {
			unlock = Data.uid++;

			// 这里就让用户无法改变的 this.expando 这个属性,
			try {
				descriptor[ this.expando ] = { value: unlock };
				//	这个方法就是只能获取,不能修改。
				Object.defineProperties( owner, descriptor );	

			// 但是这里安卓4以下无法使用,就做一个兼容写法。
			} catch ( e ) {
				descriptor[ this.expando ] = unlock;
				// 传统的方式添加。
				jQuery.extend( owner, descriptor );		//将数据添加到传入的参数中。
			}
		}

		// Ensure the cache object
		if ( !this.cache[ unlock ] ) {
			this.cache[ unlock ] = {};
		}

		return unlock;
	},

	// 设定数据。
	set: function( owner, data, value ) {
		var prop,
			unlock = this.key( owner ),
			cache = this.cache[ unlock ];

		// Handle: [ owner, key, value ] args
		if ( typeof data === "string" ) {
			cache[ data ] = value;


		// Handle: [ owner, { properties } ] args
		} else {	//Json 的形式。 
		//	这里说明:$.data(document.body, {'hg', 'wj'});	
		//	这样也可以,里面可以处理json数据格式

			// Fresh assignments by object are shallow copied
			if ( jQuery.isEmptyObject( cache ) ) {
				jQuery.extend( this.cache[ unlock ], data );
			// Otherwise, copy the properties one-by-one to the cache object
			} else {
				for ( prop in data ) {
					cache[ prop ] = data[ prop ];
				}
			}
		}
		return cache;
	},

	//获取
	get: function( owner, key ) {

		// 跟设定一样,都是通过这个key去找到id。
		var cache = this.cache[ this.key( owner ) ];

		return key === undefined ?
			cache : cache[ key ];
	},

	// 根据参数的个数,整合一下 get 和 set
	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 ||
				((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 ];

		// 不存在key的时候,就全部都干掉。
		if ( key === undefined ) {	
			this.cache[ unlock ] = {};

		} 
		// 有key的时候。
		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.
				//		这里返回一个驼峰的写法
				// 		也就是说这里的 (hg-wj === hgWj),这两个是等价的。
				name = key.concat( key.map( jQuery.camelCase ) );	
			} else {
				camel = jQuery.camelCase( key );
				// Try the string as a key before any manipulation
				if ( key in cache ) {
					name = [ key, camel ];
				} else {
					// 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( core_rnotwhite ) || [] );
				}
			}

			i = name.length;
			while ( i-- ) {
				delete cache[ name[ i ] ];
			}
		}
	},
	hasData: function( owner ) {
		return !jQuery.isEmptyObject(
			this.cache[ owner[ this.expando ] ] || {}
		);
	},
	// 删除的是一个整体, 把这个 this.expando 给干掉了。 
	discard: function( owner ) {
		if ( owner[ this.expando ] ) {
			delete this.cache[ owner[ this.expando ] ];
		}
	}
};

// These may be used throughout the jQuery core codebase
data_user = new Data();
data_priv = new Data();



例子: (1)
解释一下 defineProperty 和 freeze

Object.freeze(obj);这个函数,导致了obj无法被修改,只能阅读。

	var obj = {name: 'hg'};
	obj.name = 'wj';
	console.log(obj.name);		//wj
	Object.freeze(obj);
	obj.name = 'hg';
	console.log(obj.name);		//还是wj


Object.defineProperty(obj);
	/*三个参数。
		(1)	要锁定的对象,
		(2)	建立了 一个 对应索引值。
			0 对应了一个索引 var obj = {name: 'hg', 0 : {123} };
			给obj对象添加了一个key 和 返回值 value 。
			这么写,因为没有set方法,只能get方法,不能设置,只能获取。
		(3) 操作对象数据。
	*/
	var obj = {name: 'hg'};
	obj.name = 'wj';
	Object.defineProperty(obj, 0, {
		get : function () {
			return {0:123};
		},
	});

	console.log(obj[0]);	//Object {0: 123}
	obj[0] = 123;
	console.log(obj[0]);	//Object {0: 123} 并没有改变
// es5 中还是有很多很不错的方法,可以玩玩。




/***********************************************************************************/

	工具方法:调用的都是data对象的方法。
	jQuery.extend({
		acceptData: Data.accepts,

		hasData: function( elem ) {
			return data_user.hasData( elem ) || data_priv.hasData( elem );
		},

		data: function( elem, name, data ) {
			return data_user.access( elem, name, data );
		},

		removeData: function( elem, name ) {
			data_user.remove( elem, name );
		},

		_data: function( elem, name, data ) {
			return data_priv.access( elem, name, data );
		},

		_removeData: function( elem, name ) {
			data_priv.remove( elem, name );
		}
	});

/***********************************************************************************/

	实例方法。
	jQuery.fn.extend({
		data: function( key, value ) {
			var attrs, name,
				//	获取this
				elem = this[ 0 ],
				i = 0,
				data = null;

			// Gets all values
			//	判断key是否为空
			if ( key === undefined ) {
				// $('dsd')	--> 获取不到
				if ( this.length ) {	//	判断元素的长度。是否有元素
					data = data_user.get( elem );

					//	这里一并判断了自定义的属性。也兼容html5中的自定义属性。
					if ( elem.nodeType === 1 && !data_priv.get( elem, "hasDataAttrs" ) ) {
						attrs = elem.attributes;	//	获取所有属性的一个集合。
						for ( ; i < attrs.length; i++ ) {
							name = attrs[ i ].name;

							// 判断数据中是否包含 data-这种,给转驼峰。
							if ( name.indexOf( "data-" ) === 0 ) {
								name = jQuery.camelCase( name.slice(5) );
								// 设定到cache中。
								dataAttr( elem, name, data[ name ] );
							}
						}
						data_priv.set( elem, "hasDataAttrs", true );
					}
				}

				return data;
			}

			if ( typeof key === "object" ) {
				return this.each(function() {
					data_user.set( this, key );
				});
			}

			return jQuery.access( this, function( value ) {
				var data,
					camelKey = jQuery.camelCase( key );

				if ( elem && value === undefined ) {
					// Attempt to get data from the cache
					// with the key as-is
					data = data_user.get( elem, key );
					if ( data !== undefined ) {
						return data;
					}


					data = data_user.get( elem, camelKey );
					if ( data !== undefined ) {
						return data;
					}

					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() {

					data_user.set( this, camelKey, value );

					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 );
			});
		}
	});

例子:
	
(1)
	$('#div1').data('name', 'hg');
	$('#div1').data('age', '21');
	//	会打印所有的数据, {'name':'hg', 'age':'21'};
	console.log($('#div1').data());	

(2)
	自定义数据是h5的特性。
	<div id='hgwj' data-hgwj='hgwjtest'></div>
	alert($('#hgwj').get(0).dataset.hgwj);		//	打印出来 hgwjtest	
	
	<div id='hgwj' data-hgwj-all='hgwjtest'></div>
	alert($('#hgwj').get(0).dataset.hgwjAll);	//	打印出来 hgwjtest

(3)
  如果存入的是这两个,那么jQuery会认为是不同的。
  $('#div1').data('nameAge', 'h1');
  $('#div1').data('name-age', 'hellow');


  this.cache = {
    1 : {
        nameAge' : 'hello',
        'name-age' : 'hello'
      }
  }

 

原文地址:https://www.cnblogs.com/hgonlywj/p/4864636.html