jQuery源码 数据缓存
内存泄露的几种情况
1.循环引用
2.Javascript闭包
3.DOM插入
为了避免内存泄漏,最好不要直接在DOM元素上绑定数据,因为这样会造成
1.循环引用
2.直接暴露数据,安全性?
3.增加一堆的自定义属性标签,对浏览器来说是没意的
4.取数据的时候要对HTML节点做操作
为解决以上问题,jQuery提供数据缓存接口
jQuery.data( element, key, value )
.data( )
静态与实例方法的区别
1.jQuery.data()可以实现为dom元素或js对象添加缓存
2.$("ele").data()实是对前者的扩展,其目的是可以方便的通过选择器为多个dom元素添加缓存数据
jQuery缓存的设计思路
数据缓存,jQuery现在支持两种
1.dom元素,数据存储在jQuery.cache中。
2.普通js对象,数据存储在该对象中。
首先先要在内存中开辟一个区域,用来保存数据,jQuery用cache对象{},那么所有的数据无法就是针对cache的CURD操作了。
1:如果是DOM元素,通过分配一个唯一的关联id把DOM元素和该DOM元素的数据缓存对象关联起来,关联id被附加到以jQuery.expando的值命名的属性上,数据存储在全局缓存对象jQuery.cache中。在读取、设置、移除数据时,将通过关联id从全局缓存对象jQuery.cache中找到关联的数据缓存对象,然后在数据缓存对象上执行读取、设置、移除操作。
2:如果是Javascript对象,数据则直接存储在该Javascript对象的属性jQuery.expando上。在读取、设置、移除数据时,实际上是对Javascript对象的数据缓存对象执行读取、设置、移除操作。
3:为了避免jQuery内部使用的数据和用户自定义的数据发生冲突,数据缓存模块把内部数据存储在数据缓存对象上,把自定义数据存储在数据缓存对象的属性data上。
$().data
// 数据缓存模块
jQuery.fn.extend( {
data: function( key, value ) {
var i, name, data,
elem = this[ 0 ],
attrs = elem && elem.attributes;
// Gets all values
// 如果没有传入key,那就获取所有的value
if ( key === undefined ) {
if ( this.length ) {
data = dataUser.get( elem );
if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) {
i = attrs.length;
while ( i-- ) {
// Support: IE 11 only
// The attrs elements can be null (#14894)
if ( attrs[ i ] ) {
name = attrs[ i ].name;
if ( name.indexOf( "data-" ) === 0 ) {
name = camelCase( name.slice( 5 ) );
dataAttr( elem, name, data[ name ] );
}
}
}
dataPriv.set( elem, "hasDataAttrs", true );
}
}
return data;
}
// Sets multiple values
// 设置多个值
// 像$().data( { key1: value1, key2: value2, ... } )这种情况
if ( typeof key === "object" ) {
return this.each( function() {
dataUser.set( this, key );
} );
}
// 其实我没明白access有什么用
// 传入的回调方法其实已经足够处理设置值和传入值这两种情况了
// 而且调用access时也没传入key
// arguments.length > 1:因为$().data()这个接口有两个形参,一个是key,一个是value。如果只有一个参数的话那就是只读取,否则就是设置值
return access( this, function( value ) {
var data;
// 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 ) {
// Attempt to get data from the cache
// The key will always be camelCased in Data
data = dataUser.get( elem, key );
if ( data !== undefined ) {
return data;
}
// Attempt to "discover" the data in
// HTML5 custom data-* attrs
data = dataAttr( elem, key );
if ( data !== undefined ) {
return data;
}
// We tried really hard, but the data doesn't exist.
return;
}
// Set the data...
// 设置值
this.each( function() {
// We always store the camelCased key
dataUser.set( this, key, value );
} );
}, null, value, arguments.length > 1, null, true );
},
removeData: function( key ) {
return this.each( function() {
dataUser.remove( this, key );
} );
}
} );
jQuery.access
jQuery的方法设计大都是多用的,可以根据传递参数的个数判断是set还是get处理,不仅如此jQuery还对参数的传递类型还抽出了一个处理的方法jQuery.access,我们可以传递字符串、数组、对象等等,根据这种类型自动分解成接口所有能接受的参数。
通过access解析后的参数就能让dataUser接口所接收,此时我们可以调用数据对象接口开始对数据进行存储设置了。
/**
* Multifunctional method to get and set values of a collection
* The value/s can optionally be executed if it's a function
* @method access
* @param {参数类型} elems 元素集合
* @param {参数类型} fn 回调
* @param {参数类型} key 键
* @param {参数类型} value 值
* @param {参数类型} chainable 读取false 设置true
* @param {参数类型} emptyGet 该参数一般是不给的,当没有元素时返回undefined
* @param {参数类型} raw 函数false 字符串true
*/
var access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
var i = 0,
len = elems.length,
bulk = key == null; // key为null时bulk为true
// Sets many values
// 设置多个值
if ( toType( key ) === "object" ) {
chainable = true;
for ( i in key ) {
access( elems, fn, i, key[ i ], true, emptyGet, raw );
}
// Sets one value
// 设置一个值
} else if ( value !== undefined ) {
chainable = true;
// value如果不为函数,raw设置为true
if ( !isFunction( value ) ) {
raw = true;
}
// key为空
if ( bulk ) {
// Bulk operations run against the entire set
// 如果value为字符串直接使用回调函数进行处理,之后不再处理
if ( raw ) {
fn.call( elems, value );
fn = null;
// ...except when executing function values
// 如果value为函数会在这里针对性的包装后在下边的if进行处理
} else {
bulk = fn;
fn = function( elem, key, value ) {
return bulk.call( jQuery( elem ), value );
};
}
}
// 使用各函数的回调作为统一处理
if ( fn ) {
for ( ; i < len; i++ ) {
fn(
elems[ i ],
key,
// value为字符串:value
// value为函数:value.call( elems[ i ], i, fn( elems[ i ], key ) )
raw ? value : value.call( elems[ i ], i, fn( elems[ i ], key ) )
);
}
}
}
// 如果是set功能则返回elems本身
if ( chainable ) {
return elems;
}
// Gets
// 否则为get功能:假如使用的是text(),html()方法则直接返回文本值
if ( bulk ) {
return fn.call( elems );
}
// 否则的话返回第一个元素的参数值。其他情况直接返回undefined(比如元素未找到)
return len ? fn( elems[ 0 ], key ) : emptyGet;
};
但经过我的多次调试,我发现access接口什么事儿都没做,然后就把参数原封不动的交给回调函数执行。当然这可能仅仅是.data()这个方法中没有处理的必要,但是在其他的.attr()、.html()接口中参数是要经过处理再传给回调函数。
Data类
从源码中可以看到回调函数中是通过data = dataUser.get( elem, key );
来取值的,通过dataUser.set( this, key, value );
来设置值。这个dataUser
哪来的?
/**
* 用于构建缓存池
* @class Data
* @constructor
*/
function Data() {
this.expando = jQuery.expando + Data.uid++;
}
Data.uid = 1;
Data.prototype = {
cache: function( owner ) {
// Check if the owner object already has a cache
// this.expando在每个页面是唯一的
// 获取owner对象的缓存
var value = owner[ this.expando ];
// If not, create one
// 如果没有,创建一个
if ( !value ) {
value = {};
// We can accept data for non-element nodes in modern browsers,
// but we should not, see #8335.
// Always return an empty object.
if ( acceptData( owner ) ) {
// If it is a node unlikely to be stringify-ed or looped over
// use plain assignment
if ( owner.nodeType ) {
owner[ this.expando ] = value;
// Otherwise secure it in a non-enumerable property
// configurable must be true to allow the property to be
// deleted when data is removed
} else {
Object.defineProperty( owner, this.expando, {
value: value,
configurable: true
} );
}
}
}
return value;
},
set: function( owner, data, value ) {
var prop,
cache = this.cache( owner );
// Handle: [ owner, key, value ] args
// Always use camelCase key (gh-2257)
// 设置单个值
if ( typeof data === "string" ) {
cache[ camelCase( data ) ] = value;
// Handle: [ owner, { properties } ] args
// 设置多个值
} else {
// Copy the properties one-by-one to the cache object
for ( prop in data ) {
cache[ camelCase( prop ) ] = data[ prop ];
}
}
return cache;
},
get: function( owner, key ) {
return key === undefined ?
this.cache( owner ) :
// Always use camelCase key (gh-2257)
owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ];
},
access: function( owner, key, value ) { // ... },
remove: function( owner, key ) { // ... },
hasData: function( owner ) { // ... }
};
var dataPriv = new Data();
var dataUser = new Data();
静态与实例方法的区别


区别就是实例方法是在DOM对象上开辟一个缓存池,而静态方法是在jQuery对象上开辟一个缓存池。
个人思路
jQuery的这个做法还是将数据绑定在DOM对象上,感觉不算特别好。我个认为更好的做法是:
1.在DOM对象上绑定一个唯一标识符
div#arron = {
...
uniqueid: '1234567890'
}
2.在jQuery静态区域开辟一个缓存池
jQuery.cache = {}
3.在jQuery缓存池中开辟一个属于div#arron的缓存区域
jQuery.cache = {
1234567890: {}
}
4.在DOM对象上绑定数据
jQuery.cache = {
1234567890: {
a: '1',
b: '2'
}
}