jquery2.0.3动画(animate)源码解读与javascript基本知识学习一

浏览: 166 发布日期: 2016-11-22 分类: jquery

jquery2.0.3动画(animate)源码解读与javascript基本知识学习

一、jQuery.speed

        在该方法中,对用户输入的动画时间、动画方式、回调方法进行封装,并返回opt        

源码中涉及到的javascript基本知识:

       (1)&&和||在javascript中的使用:在javascript中||和&&不仅可以用于boolean,也不仅仅返回boolean类型。

             &&:如果第一个操作数是 Boolean 类型,而且值为 false ,那么直接返回 false。

                 如果第一个操作数是 Boolean 类型,而且值为 true,另外一个操作数是 object 类型,那么将返回这个对象。 

                 如果两个操作数都是 object 类型,那么,返回第二个对象。 
                 如果任何一个操作数是 null,那么,返回 null。 
                 如果任何一个操作数是 NaN,那么返回 NaN。 
                 如果任何一个操作数是 undefinded,那么返回 undefined。 

           ||:如果第一个操作数是 boolean 类型,而且值为 true, 那么,直接返回 true。 
                 如果第一个操作数是 Boolean 类型,而且值为 false ,第二个操作数为 object,那么返回 object 对象。 

                 如果两个操作数都是 object 类型,那么返回第一个对象。 
                 如果两个操作数都是 null,那么,返回 null。 
                 如果两个操作数都是 NaN,那么返回 NaN。 
                 如果两个操作数都是 undefined,那么,返回 undefined。 

            在javascript中||和&&为短路或和短路与,也就是说:对于&&从左往右,当出现一个操作数为false时,则不再进行后边操作数的运算。对于||来说,当出现一个操作数为true时,则不再进行后边操作数的运算。在EMCAScript中,任何非空字符串、任何非0数值、任何对象,都可以转换为true值,否则转换为false值。根据以上特性,就不能理解jquery源码中如complete: fn || !fn && easing ||jQuery.isFunction( speed ) && speed 等内容的理解了。

       (2)javascript中的apply方法和call方法

   call()方法:call 方法可以用来代替另一个对象调用一个方法。call 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。 
     如果没有提供 thisObj 参数,那么 Global 对象被用作thisObj。说明白一点其实就是更改对象的内部指针,即改变对象的this指向的内容。这在面向对象的js编程过程中有时是很有用的。

              apply()方法:如果 argArray 不是一个有效的数组或者不是 arguments 对象,那么将导致一个 TypeError。 
                               如果没有提供 argArray 和 thisObj 任何一个参数,那么 Global 对象将被用作 thisObj, 并且无法被传递任何参数。

           Javascript中的apply与call详解 (这里面讲的很详细)


/**
* 配置动画参数
* 
* 配置动画时长,动画结束回调(经装饰了),缓动算法,queue属性用来标识是动画队列
* @param  {[Number|Objecct]}   speed  [动画时长]
* @param  {[Function]}   easing [缓动算法]
* @param  {Function} fn     [动画结束会掉]
* @return {[Object]}          [description]
*/
jQuery.speed = function( speed, easing, fn ) {
	
	// speed是否为对象
	var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
		
		// complete是我们的animate的回调方法,
		// 即动画结束时的回调
		//根据目前了解程度,以下代码有点多余,不知道开发者出于什么目的这样写,待以后确认??
		complete: fn || !fn && easing ||jQuery.isFunction( speed ) && speed,
		//持继的时间,动画运行的时间。
		duration: speed,
		//找到动画中属性随时间渐变的的算法。
		easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
	};
	
	//第一:jQuery.fx.off为true时,禁止执行动画,此时任何animate的speed都为0  
        //第二:如果输入的参数为number,则动画speed为输入的数值
        //第三:如果用户输入的为"fast","slow", 从jQuery.fx.speeds里取值, 分别为600,200。否则返回jQuery.fx.speeds的默认值,默认值为400
	opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
		opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;

	//规范化opt.queue值,当opt.queue的值为undefined/null/true 时,将它的值设置为fx
	if ( opt.queue == null || opt.queue === true ) {
		opt.queue = "fx";
	}

	 // 将旧的回调(即我们添加的回调)存入opt.old
	opt.old = opt.complete;

	// 给opt.complete重新定义,
	// 在旧方法中通过装饰包装
	opt.complete = function() {
		//如果opt.old为一个方法,则以this来执行old方法。
		if ( jQuery.isFunction( opt.old ) ) {
			opt.old.call( this );
		}
		//如果opt.queue为true,则添加到队列执行 
		if ( opt.queue ) {
			jQuery.dequeue( this, opt.queue );
		}
	};
	return opt;
};


二、队列

        在jquery中,当有多个动画时,会用到队列。dequeue:从队列中取出。queue:存入队列中。

        queue方法:以用户传入的type+queue做为key值,data为value。放入队列中。

       (1)jQuey.extend(Object)和jQuery.fn.extend(Object)

                 要真正理解jQuey.extend(Object)和jQuery.fn.extend(Object),首先必须知道在jQuery中通过$("id")的方式可以返回jquery对象的原理以及javacript中prototype。在jQuery中实际有两部分,一个是jQuery另一个是jQuery.prototype。通过jQuery.extend方法扩展的方法,该方法属于jQuery的(可以理解成为java类中的静态方法),所以说extend定义的方法只能通过$、jQuery来访问(如:$.aa())。通过jQuery.fn.extend方法扩展的方法(比如:a方法),当通过$创建对象(比如:b)时,会为每一个jQuery对象b,创建一个a方法,也就是说,不同的b方法调用不同的a方法(不同的内存空间)。

jQuery.extend({
	queue: function( elem, type, data ) {
		var queue;

		if ( elem ) { //当有元素时,才进行操作   $.queue(document,"q1",aaa);document就是elem
			type = ( type || "fx" ) + "queue"; //如果没有传入q1那么type默认为fx,加上queue字符串
			queue = data_priv.get( elem, type );//去数据缓存中获取此元素的q1queue属性值,第一次时,是undefined。

			// Speed up dequeue by getting out quickly if this is just a lookup
			if ( data ) {//data就是aaa
				if ( !queue || jQuery.isArray( data ) ) { //如果取的值不存在,第一次时是不存在的。进入if语句
					//把data也就是aaa函数转换成数组形式,也就是变成[aaa()]
					queue = data_priv.access( elem, type, jQuery.makeArray(data) );
				} else {
					 //当第二次执行时,也就是$.queue(document,"q1",bbb); 
					 //因为里面已经有[aaa()]了,因此直接把data(这里是bbb函数)push到q1queue属性值中。
					 //当然这里有一个例外,比如:第二次执行时,是这样的$.queue(document,"q1",[bbb]);
					 //传入的data是个数组,这时不会走这里,而是走if语句,这样的话,会把之前的[aaa()]覆盖掉,只会有[bbb()]。
					queue.push( data );
				}
			}
			return queue || [];//返回这个队列,其实就是这个数组[aaa(),bbb()]
		}
	},

	dequeue: function( elem, type ) {
		type = type || "fx";

		var queue = jQuery.queue( elem, type ),//先获取这个q1队列的值
			startLength = queue.length,
			fn = queue.shift(),//取队列中的第一项
			
			//hooks其实是元素elem在数据缓存中的一个属性对象,如果我们调用的是$.dequeue(document,"q1") 的话,
			//那么属性对象名就是q1queueHooks
			//属性值是{empty: jQuery.Callbacks("once memory").add(function() { data_priv.remove( elem, [ type + "queue", key ] );})}。
			//因此你使用hooks.empty,其实就是q1queueHooks.empty。
			hooks = jQuery._queueHooks( elem, type ),
			next = function() { //这个next方法其实就是出队
				jQuery.dequeue( elem, type );
			};

		/* 
		 * 这里为什么会出现inprogress呢?举个例子:
		 * $(this).animate({width:300},2,function(){}).animate({left:300},2);
		 * 这个代码的意思是入队两个定时器函数,第一个定时器函数是把宽度从100变成300,
		 * 第二个定时器函数是把left从0变成300.如果这里只有入队操作,没有出队操作,
		 * 那么这两个定时器函数都不会执行,因此大家可以去看queue的实例方法,源码在下面,里面有这样一个判断:
		 * if ( type === "fx" && queue[0] !== "inprogress" ) {jQuery.dequeue( this, type );},
		 * animate的入队,默认队列为fx,而且它的队列的第一项不是inprogress,
		 * 而是第一个定时器函数,这时进入if语句,进行出队。因此才能执行第一个定时器函数。
		 * 那么第二个定时器函数来入队时,也会马上出队吗?不会,不然的话,两个定时器函数会同时执行了。
		 * 那么第二个定时器函数为什么没有立马出队,是因为第一个定时器函数出队时,会在fx队列前面添加inprogress,
		 * 因此第二个定时器函数入队时,fx队列的第一个项就是inprogress,因而不会进行出队操作。 
		 * 第一个定时器函数执行完之后,就会进行再次出队,这时第二个定时器函数就会执行了。
		 */
		if ( fn === "inprogress" ) {
			
			/* 
			 * 如果取出的队列的第一项是inprogress,这时队列是[bbb()],因为inprogress已经出队了,
			 * 就再次出队,这时bbb()出队,队列为[],fn为bbb。
			 */
			fn = queue.shift();
			startLength--;
		}

		if ( fn ) {//当数组为["inprogress"]出队时,fn = undefined,startLength=0;这时就会结束队列操作了。

			// Add a progress sentinel to prevent the fx queue from being
			// automatically dequeued
			if ( type === "fx" ) { //当是默认队列时,也就是animate操作时,就会先往队列的前面添加inprogress
				queue.unshift( "inprogress" ); //队列变成 ["inprogress"],这时就会执行bbb(),执行完之后,又出队。
			}

			// clear up the last queue stop function
			delete hooks.stop;
			//这里就会执行第一个定时器函数,执行完之后,就会调用next方法,进行出队。这时的队列是["inprogress",bbb()]
			fn.call( elem, next, hooks );
		}

		if ( !startLength && hooks ) {//当队列结束后,清理数据缓存中队列数据
			/* 
			 * 这里执行fire方法,就会触发add添加的方法,也就是data_priv.remove( elem, [ type + "queue", key ] );
			 * 把缓存数据中的所有队列信息,以及q1queueHooks一起删除掉。
			 */
			hooks.empty.fire();
		}
	},

	// not intended for public consumption - generates a queueHooks object, or returns the current one
	_queueHooks: function( elem, type ) {
		var key = type + "queueHooks";
		return data_priv.get( elem, key ) || data_priv.access( elem, key, {
			//empty的属性值是一个Callbacks对象,Callbacks的特点是可以通过它的add方法添加函数,
			//当调用Callbacks的fire方法时,就会执行add添加的方法。
			empty: jQuery.Callbacks("once memory").add(function() {
				data_priv.remove( elem, [ type + "queue", key ] );
			})
		});
	}
});


queue方法:入队并为每个元素创建hooks对象,该对象用于删除缓存中数据。

dequeue方法:出队操作。


jQuery.fn.extend({
	queue: function( type, data ) {//$(document).queue("q1",aaa);
		var setter = 2;
		
		//修正type, 默认为表示jquery动画的fx, 如果不为"fx", 
    	//即为自己的自定义动画, 一般我们用"fx"就足够了.
		if ( typeof type !== "string" ) { //当type不等于字符串时,也就是这种情况时:$(document).queue(aaa);
			data = type;
			type = "fx";
			setter--;
		}

		//只有动画的回调
		//这里判断是获取,还是设置。比如:$(document).queue("q1"),这里是获取操作,因此进入if语句。
		if ( arguments.length < setter ) {
			return jQuery.queue( this[0], type );//获取是针对一组元素的第一个元素。
		}

		return data === undefined ?
			this :
			this.each(function() {//这里就是设置操作,对每个元素都进行设置
				//调用基础队列
            	//设置动画队列缓存
            	//并返回队列总数
				var queue = jQuery.queue( this, type, data ); //入队操作,会在缓存系统中添加一个队列q1,队列中,入队aaa。
				
				//设置元素的hooks对象,会在缓存系统中添加一个hooks属性,它可以移除缓存系统中与元素this,相关的队列操作的所有数据。
				jQuery._queueHooks( this, type );
				
				//直接执行动画队列
            	//防止在执行函数的时候, 这里又进行dequeue操作, 这样会同时执行2个函数, 队列就不受控制了.
				if ( type === "fx" && queue[0] !== "inprogress" ) {//跟静态方法的queue的思路一样
					//如果队列没有被锁住, 即此时没有在执行dequeue. 移出队列里第一个函数并执行它.
					jQuery.dequeue( this, type );
				}
			});
	},
	//$(document).dequeue("q1"); 出队操作,是针对一组元素的。也就是说如果有多个document被匹配上,那么会对每个document都做出队操作
	dequeue: function( type ) {
		return this.each(function() {
			jQuery.dequeue( this, type );
		});
	},
	// Based off of the plugin by Clint Helfers, with permission.
	// http://blindsignals.com/index.php/2009/07/jquery-delay/
	
	/* 
	 *第一个参数是延迟的时间,第二个参数是哪个队列(队列的名字)延迟,我们先来举个例子说下delay方法的作用:
	 * $(this).animate({width:300},2).delay(2).animate({left:300},2);
	 * 这个代码的意思是:第一个定时器函数执行结束后,会延迟两秒钟,才会执行第二个定时器函数。
	 */
	delay: function( time, type ) {
		 //jQuery.fx.speeds = {slow: 600,fast: 200,_default: 400};
		 //意思就是说,你delay里面是否写了"slow","fast",或"_default"。
		 //如果是,就直接调用默认的值,如果传入的是数字,那么就只用数字。
		time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
		type = type || "fx";

		return this.queue( type, function( next, hooks ) {
			//延迟time秒,再进行出队。意思就是time秒后,第二个定时器函数才会执行
			var timeout = setTimeout( next, time );
			hooks.stop = function() { //这个方法会清除定时器,如果执行,next方法就不会执行,也就不会出队了
				clearTimeout( timeout );
			};
		});
	},
	clearQueue: function( type ) {
		return this.queue( type || "fx", [] );  //把队列变成空数组,上面说到,如果传入数组,会覆盖队列的原数组
	},
	// Get a promise resolved when queues of a certain type
	// are emptied (fx is the type by default)
	/* 
	 *type是指队列的名字,如果此type的队列全部出队后,就会执行done添加的方法。
	 *我们先举个例子说下这个方法的作用:$(this).animate({width:300},2).animate({left:300},2);$(this).promise().done(function(){alert(3)});
	 *这句代码的意思是,等上面两个定时器函数都执行结束后(因为他们默认处理的都是fx队列)。才会执行弹出3的函数。
	 */
	promise: function( type, obj ) {
		var tmp,
			count = 1,
			defer = jQuery.Deferred(),//新建一个延迟对象
			elements = this,
			i = this.length, //元素的个数,这里假设是一个document元素
			resolve = function() {
				if ( !( --count ) ) {
					defer.resolveWith( elements, [ elements ] );
				}
			};

		if ( typeof type !== "string" ) { //如果没传入队列名,就用fx默认队列
			obj = type;
			type = undefined;
		}
		type = type || "fx";

		while( i-- ) { //执行一次
			tmp = data_priv.get( elements[ i ], type + "queueHooks" ); //去缓存系统找跟这个元素有关的数据
			if ( tmp && tmp.empty ) {//如果存在,就证明队列中有定时器函数要执行。进入if语句
				count++;//count等于2
				/* 
				 *当调用tmp.empty.fire方法时,就会执行resolve 方法。
				 * 而这里会等fx类型的队列全部出队后(这两个定时器函数都执行结束后),才会触发fire方法,
				 * 这时就会执行add添加的所有方法,resolve就是其中一个,于是count就会变成0(在出队列时,下面的resolve方法已经执行一次了),
				 * 进入if语句,执行延迟对象的resolveWith,而此方法,就会触发延迟对象的done方法添加的函数,因此弹出3的函数执行。
				 */
				tmp.empty.add( resolve );
			}
		}
		resolve(); //这里会先执行一次resolve方法,count--,变成1。
		return defer.promise( obj ); //返回这个延迟对象。
	}
});



三、jquery中数据缓存Data

       在jQuery数据缓存实现中,在Date中为每个DOM元素(owner)提供了cache对象以及expando属性。expando的值为cache缓存对象的key值,该key值为Data.uid循环+1后的数值。也就是说,通过expando找到cache的key值,通过key值找到缓存数据。

      

function Data() {//先在jQuery内部创建一个cache对象{}, 来保存缓存数据。 然后往需要进行缓存的DOM节点上扩展一个值为expando的属性
	// Support: Android < 4,
	// Old WebKit does not have Object.preventExtensions/freeze method,
	// return new empty object instead with no [[set]] accessor
	//给Data添加了一个cache对象,并且给这个对象添加了一个属性0,默认值为{}
	Object.defineProperty( this.cache = {}, 0, {
		get: function() {
			return {};
		}
	});
        //expando的值,用于把当前数据缓存的UUID值做一个节点的属性给写入到指定的元素上形成关联桥梁 
	this.expando = jQuery.expando + Math.random();
}

(1)Object.defineProperty:将属性添加到对象,或修改现有属性的特性。该方法输入三个参数:

             obj:待修改的属性名称、prop:待修改的属性名称、descriptor:待修改属性的相关描述

             descriptor要求传入一个对象,其没如下         

{
configurable:false,
enumerable:false,
writable:false,
value:null,
set:undefined,
get:undefined
}

  1. configurable ,属性是否可配置。可配置的含义包括:是否可以删除属性( delete ),是否可以修改属性的writable 、 enumerable 、 configurable 属性。
  2. enumerable ,属性是否可枚举。可枚举的含义包括:是否可以通过 for...in 遍历到,是否可以通过Object.keys() 方法获取属性名称。
  3. writable ,属性是否可重写。可重写的含义包括:是否可以对属性进行重新赋值。
  4. value ,属性的默认值。
  5.  set ,属性的重写器(暂且这么叫)。一旦属性被重新赋值,此方法被自动调用。
  6.  get ,属性的读取器(暂且这么叫)。一旦属性被访问读取,此方法被自动调用

 (2)   nodeType属性

//这个id的值就作为cache的key用来关联DOM节点和数据,也就是说cache[id]就取到了这个节点上的所有缓存
//关联起dom对象与数据缓存对象的一个索引标记,换句话说.先在dom元素上找到expando对应值,也就uid,然后通过这个uid找到数据cache对象中的内容
Data.uid = 1;

Data.accepts = function( owner ) {
	//官方的注释已经讲的很明白了
	//Data.occepts方法传入的参数为 element或document类型结点。或者为任意对象时,返回true。
	//  Accepts only:
	//  - Node
	//    - Node.ELEMENT_NODE
	//    - Node.DOCUMENT_NODE
	//  - Object
	//    - Any
	return owner.nodeType ?
		owner.nodeType === 1 || owner.nodeType === 9 : true;
};

key方法:在key方法中为每个DOM元素中定义的cache和expando属性进行赋值。如果DOM元素(owner)的expando值为undefined,则将Data.uid++后的值赋值给expando,并将key值expando对应的cache数据缓存对象进行初始化。key方法返回key值(expando的值或Data.uid++后的值)。

set方法:在set方法中,调用key方法获取expando值(key值),并取出key值对应的数据缓存对象。在将data数据添加到数据缓存中。

get方法:在get方法中,调用key方法获取expando值(key值),并取出key值对应的数据缓存对象。再根据缓存数据中的key值(与之前的那个key不是同一个key)取出数据。

access方法:在access方法中,调用set方法,将data(key:value)添加到cache中。返回:如果value等于undefined则返回key,否则返回value值。

hasData方法:检查owner中是否有cache数据缓存对象,有则返回。没有返回空对象。

discard方法:如果owner中有expando值,则删除该值对应cache缓存数据。

Data.prototype = {
	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 ) ) {
			return 0;
		}

		var descriptor = {},
			//检查 owner object 是否已经有一个 cache key
			unlock = owner[ this.expando ];

		// If not, create one
		if ( !unlock ) {
			unlock = Data.uid++;

			//安全性检查,为非可枚举或非重写的属性   non-enumerable, non-writable property
			try {
				//将owner对象的expando属性的value值,设置为Data.uid
				descriptor[ this.expando ] = { value: unlock };
				Object.defineProperties( owner, descriptor );

			// Support: Android < 4
			// Fallback to a less secure definition
			} catch ( e ) {
				descriptor[ this.expando ] = unlock;
				jQuery.extend( owner, descriptor );
			}
		}

		//对owner的cache进行初始化
		if ( !this.cache[ unlock ] ) {
			this.cache[ unlock ] = {};
		}

		return unlock;
	},
<span style="white-space:pre">	</span>set: function( owner, data, value ) {
<span style="white-space:pre">		</span>var prop,
<span style="white-space:pre">			</span>// There may be an unlock assigned to this node,
<span style="white-space:pre">			</span>// if there is no entry for this "owner", create one inline
<span style="white-space:pre">			</span>// and set the unlock as though an owner entry had always existed
<span style="white-space:pre">			</span>//取出owner(DOM元素)中expando属性的值,也就是在Data.uid++后的值。
<span style="white-space:pre">			</span>unlock = this.key( owner ),
<span style="white-space:pre">			</span>//根据expando的值,取出对应的缓存数据
<span style="white-space:pre">			</span>cache = this.cache[ unlock ];


<span style="white-space:pre">		</span>//Handle: [ owner, key, value ] args  data为key键,value为值
<span style="white-space:pre">		</span>if ( typeof data === "string" ) {
<span style="white-space:pre">			</span>cache[ data ] = value;


<span style="white-space:pre">		</span>// Handle: [ owner, { properties } ] args
<span style="white-space:pre">		</span>} else {
<span style="white-space:pre">			</span>/* 
<span style="white-space:pre">			</span> * 如果cache为空对象,则将data直接添加到cache缓存数据中
<span style="white-space:pre">			</span> */
<span style="white-space:pre">			</span>if ( jQuery.isEmptyObject( cache ) ) {
<span style="white-space:pre">				</span>jQuery.extend( this.cache[ unlock ], data );
<span style="white-space:pre">			</span>// Otherwise, copy the properties one-by-one to the cache object
<span style="white-space:pre">			</span>} else {
<span style="white-space:pre">				</span>for ( prop in data ) {
<span style="white-space:pre">					</span>cache[ prop ] = data[ prop ];
<span style="white-space:pre">				</span>}
<span style="white-space:pre">			</span>}
<span style="white-space:pre">		</span>}
<span style="white-space:pre">		</span>return cache;
<span style="white-space:pre">	</span>},	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 ||
				((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 ) );
			} 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 ] ] || {}
		);
	},
	discard: function( owner ) {
		if ( owner[ this.expando ] ) {
			delete this.cache[ owner[ this.expando ] ];
		}
	}
};






返回顶部