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

来源:互联网 发布:淘宝怎么找套现的店铺 编辑:程序博客网 时间:2024/05/17 02:50

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的默认值,默认值为400opt.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 时,将它的值设置为fxif ( opt.queue == null || opt.queue === true ) {opt.queue = "fx";} // 将旧的回调(即我们添加的回调)存入opt.oldopt.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就是elemtype = ( 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 lookupif ( data ) {//data就是aaaif ( !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 dequeuedif ( type === "fx" ) { //当是默认队列时,也就是animate操作时,就会先往队列的前面添加inprogressqueue.unshift( "inprogress" ); //队列变成 ["inprogress"],这时就会执行bbb(),执行完之后,又出队。}// clear up the last queue stop functiondelete 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//    - Anyreturn 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 keyunlock = owner[ this.expando ];// If not, create oneif ( !unlock ) {unlock = Data.uid++;//安全性检查,为非可枚举或非重写的属性   non-enumerable, non-writable propertytry {//将owner对象的expando属性的value值,设置为Data.uiddescriptor[ 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 keysif ( 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 manipulationif ( key in cache ) {name = [ key, camel ];} else {// If a key with the spaces exists, use it.// Otherwise, create an array by matching non-whitespacename = 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 ] ];}}};






0 0
原创粉丝点击