数据缓存系统最早应该是jQuery1.2引入的,那时它的事件系统完成照搬DE大神的addEvent.js,而addEvent在实现有个缺憾,它把事件的回调都放到EventTarget之上,这会引发循环引用,如果EventTarget是window对象,又会引发全局污染。有了数据缓存系统,除了规避这两个风险外,我们还可以有效地保存不同方法产生的中间变量,而这些变量会对另一个模块的方法有用,解耦方法间的依赖。对于jQuery来说,它的事件克隆乃至后来的列队实现都是离不开缓存系统。
jQuery1.2 在core模块新增了两个静态方法, data与removeData。data不用说,与jQuery其他方法一样,读写结合。jQuery的缓存系统是把所有数据都放$.cache之上,然后为每个要使用缓存系统的元素节点,文档对象与window对象分配一个UUID。UUID的属性名为一个随机的自定义属性,"jQuery" + (new Date()).getTime(), 值为整数,从零递增。但UUID总要附于一个对象上,如果那个对象是window,岂不是全局污染吗,因此jQuery内部判定它是window对象时,映射为一个叫windowData的空对象,然后UUID加在它之上。有了UUID,我们在首次访问缓存系统时,会在$.cache对象开辟一个空对象(缓存体),用于放置与目标对象有关的东西。这有点像银行开户了,UUID的值就是存折。removeData则会删掉不再需要保存数据,如果到最后,数据删清光了,它也没有任何键值对,成为空对象,jQuery就会从$.cache中删掉此对象,并从目标对象移除UUID。
复制代码 代码如下:
//jQuery1.2.3
var expando = "jQuery" + (new Date()).getTime(), uuid = 0, windowData = {};
jQuery.extend({
cache: {},
data: function( elem, name, data ) {
elem = elem == window ? windowData : elem;//对window对象做特别处理
var id = elem[ expando ];
if ( !id ) //如果没有UUID则新设一个
id = elem[ expando ] = ++uuid;
//如果没有在$.cache中开户,则先开户
if ( name && !jQuery.cache[ id ] )
jQuery.cache[ id ] = {};
// 第三个参数不为undefined时,为写操作
if ( data != undefined )
jQuery.cache[ id ][ name ] = data;
//如果只有一个参数,则返回缓存对象,两个参数则返回目标数据
return name ? jQuery.cache[ id ][ name ] : id;
},
removeData: function( elem, name ) {
elem = elem == window ? windowData : elem;
var id = elem[ expando ];
if ( name ) {//移除目标数据
if ( jQuery.cache[ id ] ) {
delete jQuery.cache[ id ][ name ];
name = "";
for ( name in jQuery.cache[ id ] )
break;
//遍历缓存体,如果不为空,那name会被改写,如果没有被改写,则!name 为true,
//从而引发再次调用此方法,但这次是只传一个参数,移除缓存体,
if ( !name )
jQuery.removeData( elem );
}
} else {
//移除UUID,但IE下对元素使用delete会抛错
try {
delete elem[ expando ];
} catch(e){
if ( elem.removeAttribute )
elem.removeAttribute( expando );
}//注销账户
delete jQuery.cache[ id ];
}
}
})
jQuery在1.2.3中添加了两个同名的原型方法data与removeData,目的是方便链式操作与集化操作。并在data中添加getData, setData的自定义事件的触发逻辑。
1.3中,数据缓存系统终于独立成一个模块data.js(内部开发时的划分),并添加了两组方法,命名空间上的queue与dequeue,原型上的queue与dequeue。queue的目的很明显,就是缓存一组数据,为动画模块服务。dequeue是从一组数据中删掉一个。
复制代码 代码如下:
//jQuery1.3
jQuery.extend({
queue: function( elem, type, data ) {
if ( elem ){
type = (type || "fx") + "queue";
var q = jQuery.data( elem, type );
if ( !q || jQuery.isArray(data) )//确保储存的是一个数组
q = jQuery.data( elem, type, jQuery.makeArray(data) );
else if( data )//然后往这个数据加东西
q.push( data );
}
return q;
},
dequeue: function( elem, type ){
var queue = jQuery.queue( elem, type ),
fn = queue.shift();//然后删掉一个,早期它是放置动画的回调,删掉它就call一下,
// 但没有做是否为函数的判定,估计也没有写到文档中,为内部使用
if( !type || type === "fx" )
fn = queue[0];
if( fn !== undefined )
fn.call(elem);
}
})
fx模块animate方法的调用示例:
复制代码 代码如下:
//each是并行处理多个动画,queue是一个接一个处理多个动画
this[ optall.queue === false ? "each" : "queue" ](function(){ /*略*/})
在元素上添加自定义属性,还会引发一个问题。如果我们对这个元素进行拷贝,就会将此属性也会复制过去,导致两个元素都有相同的UUID值,出现数据被错误操作的情况。jQuery早期的复制节点实现非常简单,如果元素的cloneNode方法不会复制事件就使用cloneNode,否则使用元素的outerHTML,或父节点的innerHTML,用clean方法解析一个新元素出来。但outerHTML与innerHTML都会显式属性写在里面,因此需要用正则把它们清除掉。
复制代码 代码如下: