Home  >  Article  >  Web Front-end  >  jQuery source code learning queue module queue

jQuery source code learning queue module queue

巴扎黑
巴扎黑Original
2017-06-20 11:26:321038browse

1. Queue module of jQuery

jQuery’s queue module mainly provides support for the animation module EFFECTS. A separate namespace is extracted so that programmers can Customize your own queue.

For specific API calling methods, please refer to this blog http://snandy.iteye.com/blog/1978428

2. Queue Module code structure

Low-level methods jQuery has three methods: queue, dequeue, and _queueHooks; low-level methods are not recommended to be called directly externally;

Advanced methods include .queue,.dequeue,.clearQueue,.delay,.promise

3. Implementation code

For more attention points, please see the comments in the code for ideas


   jQuery.fn.extend({        //.queue([queuename]);返回第一个匹配元素关联的函数队列        //.queue([queueName],newQueue);修改匹配元素关联的函数队列,使用函数数组newQueue替换当前队列        //.queue([queueName],callback(next,hooks));修改匹配元素关联的函数队列,添加callback到队列中        //如果queueName省略,则默认是动画队列fx        queue:function(type,data){            var setter=2;            if(typeof type!=='string'){                //进行参数修正
                data=type;
                type='fx';
                setter--;
            }            //靠,这种判断是获取还是设置的点子是怎么想出来的
            if(arguments.length<setter){                //说明是获取操作,根据jQuery的思想,获取的时候,仅获取首个
                return jQuery.queue(this[0],type);
             }             //否则说明是设置操作,根据jQuery的思想,设置的时候,进行遍历设置
             if(data){                this.each(function(i,item){                    var queue=jQuery.queue(this,type,data);                    //确定添加了hooks
                    jQuery._queueHooks(this,type);                    //如果是动画队列,那么首次入队的时候回自动出队执行,不必手动调用dequeue
                    if(type===&#39;fx&#39;&&queue[0]!==&#39;inprogress&#39;){
                        jQuery.dequeue(this,type);
                    }
                });
             }

        },
        dequeue:function(type){            this.each(function(){
                jQuery.dequeue(this,type);
            });
        },        //使得队列中下一个函数延迟执行        delay:function(time,type){
            time=jQuery.fx?jQuery.fx.speeds[time]||time:time;
            type=type||&#39;fx&#39;;            //next和hooks的参数赋值是在dequeue的fn.call中,还记得么?
            return this.queue(type,function(next,hooks){                var timerId=setTimeout(next,time);
                hooks.stop=function(){
                    clearTimeout(timerId);
                }
            });
        },
        clearQueue:function(type){            this.queue(type||&#39;fx&#39;,[]);
        },        //针对每一个匹配元素,对其添加监控,当所有匹配元素的type队列中的函数都执行完毕时,调用Promise的done添加的成功回调函数        promise:function(type,obj){            var elems=this,
                count=0,
                i=elems.length,
                defered=jQuery.Deferred(),
                hook;            if(typeof type!==&#39;string&#39;){
                obj=type;
                type=undefined;
            }
            type=type||&#39;fx&#39;;
            function resolve(){                if(!(--count)){                    //如果计数器count变为0                    defered.resolveWith(elems,[elems]);
                }
            }            //添加监控
            while(i--){
                hook=elems[i]&&data_priv.get(elems[i],type+&#39;queueHooks&#39;);                if(hook&&hook.empty){
                    count++;
                    hook.empty.add(resolve);
                }
            }            //这里为毛要调用一次呢?           // resolve();
            return defered.promise(obj);

        }
    });

The overall view of myJquey.js code so far


(function(window,undefined){    var rootjQuery,
        core_version=&#39;2.0.3&#39;,
        idExpr=/^#([\w\-]*)$/,        //下面两个正则用于转驼峰
        rmsPrefix = /^-ms-/,
        rdashAlpha = /-([\da-z])/gi,
        rnotwhite = /\S+/g,//匹配非空白字符
        class2type={},
        core_deletedIds=[],
        core_version=&#39;2.0.3&#39;,

        _jQuery=window.jQuery,
        _$=window.$,

        core_toString=class2type.toString,
        core_hasOwn=class2type.hasOwnProperty,
        core_trim=core_version.trim,
        core_indexOf=core_deletedIds.indexOf,
        core_push=core_deletedIds.push,
        core_concat=core_deletedIds.concat,
        core_slice=core_deletedIds.slice,        //用于jQuery.camelCase转驼峰函数中        //当replace函数只有一个匹配项时,第二个参数可以是一个函数        //如果repalce中的正则没有捕获组,会向这个函数传递三个参数:模式的匹配项,模式匹配项在字符串中的位置,原始字符串        //如果replace中的正则有捕获组,也会向这个函数传递三个参数,模式的匹配项,捕获组的匹配项,模式匹配项在字符串中的位置
        fcamelCase=function(all,letter){           return letter.toUpperCase();            
        },
        jQuery=function(selector,context){            return new jQuery.fn.init(selector,context,rootjQuery);
        };     
    //jQuery相关实例方法和属性
    jQuery.fn=jQuery.prototype={
        jQuery:core_version,//其实就是版本字符串2.0.3
        constructor:jQuery,//还原constructor指向
        selector:&#39;&#39;,//含有连续的整型属性、length属性、context属性,selector属性(在jQuery.fn.init中设置),preObject属性(在pushStack中设置)
        length:0,
        init:function(selector,context,rootjQuery){            var match,elem;           //selector是选择器表达式
           if(!selector){            return this;
           }           if(typeof selector ===&#39;string&#39;){
                match=idExpr.exec(selector);
                context=context||document;                if(match){
                    elem=context.getElementById(match[1]);                    if(elem&&elem.parentNode){                        this[0]=elem;                        this.length=1;                        
                    }                    this.selector=selector;                    this.context=document;                    return this;
                }else{                    //说明是复杂的选择器表达式,这里只考虑javascript原声方法                    //querySelectorAll返回所有匹配元素的nodelist                    //querySelector返回匹配的第一个元素
                    return jQuery.merge(this,context.querySelectorAll(selector));
                    
                }                
           }           //处理selector是DOM元素的情形
           if(selector&&selector.nodeType){                this[0]=selector;                this.length=1;                this.context=selector;                return this;
           }           //处理selector是函数的情形
           if(jQuery.isFunction(selector)){                return rootjQuery.ready( selector );
           } 
           //处理selector是jQuery对象的情形
           if(selector.selector){                this.selector=selector.selector;                this.context=selector.context;
           }           //处理其他情形
           return jQuery.makeArray(selector,this);

        },        //将jQuery类数组对象转换为数组        toArray:function(){            return core_slice.call(this);
        },        //如果传递了参数num,代表获取下标num的DOM元素(num可以为负数)        //如果没有传递num,则将jQuery对象转换为数组后整体返回
        get:function(num){            if(num==null){//注意这里不能用!num,因为num可以为0
                return this.toArray();
            }            return num<0?this[num+this.length]:this[num];
        },        //入栈        pushStack:function(elems){            var ret=jQuery.merge(this.constructor(),elems);
            
            ret.prevObject=this;
            ret.context=this.context;            return ret;
        },        //遍历jQuery对象        each:function(callback,args){            //在静态方法已经指定了callback的执行上下文
           return jQuery.each(this,callback,args);
        },        //加载完成事件方法,这里暂不考虑        ready:function(fn){},
        slice:function(){      
            //注意apply和call的区别                          
            return this.pushStack(core_slice.apply(this,arguments));
        },
        first:function(){            return this.get(0);
        },
        last:function(){            return this.get(-1);
        },
        eq:function(i){            var length=this.length,
                j=+i+(i<0?length:0);            return this.pushStack(j>=0&&j<length?[this[j]]:[]);
        },
        map:function(callback){            //这种写法不能指定callback的执行环境,因为在静态方法jQuery.map并没有指定callback的执行上下文            // return this.pushStack(jQuery.map(this,callback));
           return this.pushStack(jQuery.map(this,function(elem,i){                 
                return callback.call(elem,i,elem);
           }));
        },        //与pushStack方法相对应,返回栈的上一级        end:function(){            return this.prevObject||this.constructor();
        },        
        push:core_push,
        sort:[].sort,
        splice:[].splice,
    };
    jQuery.fn.init.prototype=jQuery.fn; 


    //可接受的参数类型如下:jQuery.extend([deep],target,object1,[objectN])
    jQuery.extend=jQuery.fn.extend=function(){        var target=arguments[0]||{},//指向目标对象
            deep=false,//是否进行深度复制
            i=1,//表示源对象的起始下标
            length=arguments.length,//表示参数个数;
            options,name,src,copy,copyIsArray;//options指向某个源对象,name指向源对象的某个属性名,src目标对象某个属性的原始值,copy某个源对象的某个属性的值,copyIsArray指示变量copy是否为数组        
        //首先进行参数修正
        if(typeof target===&#39;boolean&#39;){
            deep=target;
            target=arguments[1]||{};
            i=2;
        }        //此时target就是jQuery或jQuery.fn
        if(i===length){
            target=this;
            i--;
        }        //处理target是字符串或者其他情形,这在深度复制中可能出现        // if(typeof target!==&#39;object&#39;||!jQuery.isFunction(target)){        //     target={};        // }
        for(i;i<length;i++){
            options=arguments[i];            for(name in options){
                src=target[name];
                copy=options[name];                if(deep&&copy&&(jQuery.isPlainObject(object)||(copyIsArray=jQuery.isArray(object)))){                    if(copyIsArray){
                        copyIsArray=false;
                        clone=src&&jQuery.isArray(src)?src:[];
                    }else{
                        clone=src&&jQuery.isPlainObject(src)?src:{};
                    }
                    target[name]=jQuery.extend(deep,clone,copy);
                }else{
                    target[name]=copy;
                }
            }    
        }        return target;
    };    //检查是否是数组或者类数组    function isArrayLike(obj){        var length=obj.length,
            type=jQuery.type(obj);        if(obj&&jQuery.isWindow(obj)){            return false;
        }        if(obj.nodeType===1&&length){            return true;
        }        

        if(type===&#39;array&#39;){            return true;
        }        if(typeof length===&#39;number&#39;&&(length==0||(length>0&&(length-1) in obj))){            return true;
        }        
        return false;
    }
    jQuery.extend({        //一堆静态方法和属性
        expando:'jQuery'+(core_version+Math.random()).replace(/\D/g,''),        // 该函数用于释放jQuery对于全局变量$的控制权,可选的参数deep代表是否释放对全局变量jQuery的控制权        noConflict:function(deep){            if(window.$===jQuery){
                window.$=_$;
            }            if(deep&&window.jQuery===jQuery){
                window.jQuery=_jQuery;
            }            return jQuery;
        },        /********isReady,readyWait,holdReay,ready与加载事件有关,暂且略过***********/
        isReady:false,
        readyWait:1,
        holdReady:function(hold){},
        ready:function(){},        /*******/


        /****下面是一系列类型检测的静态方法*******/
        isFunction:function(obj){            //如果使用typeof,在有些浏览器中,正则也会返回function,因此这里采用jQuery处理后的方法,jQuery.type
            return jQuery.type(obj)==='function';
        },
        isArray:Array.isArray,
        isWindow:function(obj){            return obj!==null&&obj===obj.window;
        },        //判断obj是否为数字或者数字类型的字符串,并且是有效数字        isNumeric:function(obj){            return !isNaN(parseFloat(obj))&&isFinite(obj);
        },
        type:function(obj){            if(obj===null){                return String(null);
            }            //Date,Array等类型typeof都会返回object,function、正则(部分浏览器)中 typeof都会返回function
             
            if(typeof obj==='object'||typeof obj==='function'){                
                return class2type[core_toString.call(obj)]||'object';
            }            return typeof obj;
        },        //判断是否为以下两种情况:1,对象字面量;2,通过new Object()创建        isPlainObject:function(obj){            if(jQuery.type(obj)!=='object'||obj.nodeType||jQuery.isWindow(obj)){                return false;
            }            //如果是纯粹的对象,那么obj一定有constructor属性,并且方法hasOwnPropertyOf一定就在构造函数本身的原型中,而不用通过原型链查找得到
           if(obj.constructor&&!core_hasOwn.call(obj.constructor.prototype,'isPrototypeOf')){                return false;
           }           return true;

        },        //检查是否是空对象        isEmptyObject:function(obj){            for(var name in obj){                return false;
            }            return true;
        },        /******类型检测静态方法结束********/

        error:function(msg){            throw new Error(msg);
        },        //将html字符串转换为html DOM结构,        parseHTML: function( data, context, keepScripts ){

        },
        parseJSON:JSON.parse,
        parseXML:function(data){            var xml, tmp;            if ( !data || typeof data !== "string" ) {                return null;
            }            // Support: IE9
            try {
                tmp = new DOMParser();
                xml = tmp.parseFromString( data , "text/xml" );
            } catch ( e ) {
                xml = undefined;
            }            if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) {
                jQuery.error( "Invalid XML: " + data );
            }            return xml;
        },
        noop:function(){},        //用于在全局作用域执行javascript代码,这里暂略        globalEval:function(data){},        //转换连字符字符串为驼峰类型
        camelCase:function(string){            return string.replace(rmsPrefix,'ms-').replace(rdashAlpha,fcamelCase);
        },        //判断elem的nodeName是否=name        nodeName:function(elem,name){            return elem.nodeName&&elem.nodeName.toLowerCase()==name.toLowerCase();
        },        //jQuery遍历方法,其中args是传递给回调callback的参数,仅供jQuery内部使用;外部调用该方法时,回调的参数默认为数组下标/对象key,对应数组值/对象value
        each:function(object,callback,args){            var i,
                value, 
                length=object.length,
                isArray=isArrayLike(object);            if(args){//说明是内部调用
                if(isArray){                    for(i=0;i<length;i++){
                       value= callback.call(object[i],args);                       if(value===false){                            break;
                       }
                    }
                }else{                    for(i in object){
                        value=callback.call(object[i],args);                        if(value===false){                            break;
                        }
                    }
                }
            }else{                if(isArray){                    for(i=0;i<length;i++){
                        value=callback.call(object[i],i,object[i]);                        if(value===false){                            break;
                        }
                    }
                }else{                    for(i in object){
                        value=callback.call(object[i],i,object[i]);                        if(value===false){                            break;
                        }
                    }
                }
            }            return object;
        },
        trim:function(str){            return str==null?&#39;&#39;:core_trim.call(str);
        },        //将一个类数组对象转换为真正的对象        //results参数仅供jquery内部使用,此时在该参数的基础上添加元素        makeArray:function(array,results){            var ret=results||[],
                type=jQuery.type(array);            //undefined,null都会==null
            if(array!=null){                //1,没有length属性,或者具有length属性,但是是以下几种情况的                //2.如果array是string 的length表示字符串的长度                //3.如果array是函数,其length代表函数生命时的参数个数                //4,如果array是window对象,属性Length返回窗口中的框架(frame,iframe)个数
                if(array.length==null|| type==&#39;string&#39; || type==&#39;function&#39; ||type==&#39;regexp&#39;||jQuery.isWindow(array)){
                    core_push.call(ret,array);
                }else{//否则说明是类数组对象                    jQuery.merge(ret,array);
                }
            }            return ret;
        },
          
        inArray:function(elem,array,i){            return array==null?-1:core_indexOf.call(array,elem,i);
        },        //用于合并两个数组的元素到第一个数组中        //事实上,jquery源代码中第一个参数可以是数组或者类数组对象,第二个参数可以是数组、类数组对象或任何含有连续整型属性的对象        //第一个参数是数组,最后返回数组;第一个参数是类数组,则返回类数组        merge:function(first,second){            var l=second.length,
                i=first.length,
                j;            if(typeof l==&#39;number&#39;){                for(j=0;j<l;j++){
                    first[i++]=second[j];
                }   
            }else{                while(second[j]!=undefined){
                    first[i++]=second[j++];
                }
            }
            
            first.length=i;            return first;
        },        //用于查找数组中满足过滤函数的元素,形成新的数组之后返回,原数组不受影响        //如果inv未传入或者是false,元素只有在过滤函数返回true时,才会被保存在最终的结果数组中        //如果参数inv是true,则恰好相反        grep:function(elems,callback,inv){            var i,
                ret=[],
                length=elems.length,
                retVal;
            inv=!!inv;            for(i=0;i<length;i++){
                retVal=!!callback.call(elems[i],i);                if(retVal!==inv){
                    ret.push(elems[i]);
                }
            }            return ret;             
        },        //用于对数组中每个元素执行callback操作,并将结果形成新的数组返回        //参数arg仅仅是jQuery内部使用        map:function(elems,callback,arg){            var ret=[],
                retVal,
                i,
                length=elems.length,
                isArray=isArrayLike(elems);            if(isArray){                for(i=0;i<length;i++){
                    retVal=callback(elems[i],i,arg);//注意不是callback.call
                    if(retVal!=null){
                        ret.push(retVal);
                    }
                }
            }else{                for(i in elems){
                    retVal=callback(elems[i],i,arg);                    if(retVal!=null){
                        ret.push(retVal);
                    }
                }
            }            //保证最终返回的是一维数组
            return core_concat.call([],ret);
        },
        guid:1,        //该方法用于更改函数的执行上下文        //源代码中有两种传参形式,这里仅考虑最常见的一种        proxy:function(fn,context){            if(!jQuery.isFunction(fn)){                return undefined;
            }            var args=core_slice.call(arguments,2);
                proxy=function(){                    return fn.call(context||this,core_concat.call(args,core_slice.call(arguments)));
                };
            proxy.guid=fn.guid=fn.guid||jQuery.guid++;            return proxy;
        },        //用一个方法同时实现get和set操作        //如何设置或者获取由回调函数fn确定        //这个方法的实现等用到的时候结合来看        access: function( elems, fn, key, value, chainable, emptyGet, raw ){
            
        },
        now:Date.now,        //该方法用于交换css样式,在support模块较多用到        //要交换的样式由参数options传递        swap: function( elem, options, callback, args ){            var name,ret,
                old={};            for(name in options){
                old[name]=elem.style[name];
                elem.style[name]=options[name];
            }
            ret=callback.call(elem,args||[]);            for(name in options){
                elem.style[name]=old[name];
            }            return ret;
        },
        
    });    //目前,js中typeof的返回值有六种:"number," "string," "boolean," "object," "function," 和 "undefined."    //通过object.prototype.toString/或者{}.toString 返回值有九种:Boolean Number String Function Array Date RegExp Object Error,其中的Array,Date,RegExp,Object,Error都属于Object类型,在有些浏览器中typeof 正则会返回function
    jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(i,name){
        class2type["[object "+name+"]"]=name.toLowerCase();
    });    //console.log(class2type,class2type);
    rootjQuery=jQuery(document);    /****接下来这一部分,在jQuery的源代码中,本来是Sizzle,这里暂且略过***/
   var optionsCache={};
   function createOptions(options){        var object=optionsCache[options]={};        //\S+,匹配非空格字符        //正则表达式如果没有g,仅匹配第一个匹配项
        jQuery.each(options.match(/\S+/g),function(i,item){            object[item]=true;
        });        return object;
   }   //参数options可以是字符串或者是对象形式,可选属性/字符串组合有   //once:回调函数列表只能执行一次   //memory:fire调用之后,再次add将立即触发   //unique:同一个函数不能被重复添加到回调函数列表中   //stopOnFlase:当某一个函数返回false时,为true的时候,回调函数列表的执行终止
   jQuery.Callbacks=function(options){
        options=typeof options===&#39;string&#39;?optionsCache[options]||createOptions(options):options||[];        var list=[],//用于存储回调函数列表            firingStart,
            once=options.once,
            memory,//初始值为undefined,只有在memory模式下,调用fire后才会被赋值,以备在add中再次调用           
            fired=false,//指示是否fire过
            firingIndex,//指向要执行的下一个回调函数的下标
            add=function(arg){                
                var type;
                jQuery.each(arg,function(i,item){
                    type=jQuery.type(item);                    if(type===&#39;function&#39;&&!(options.unique&&self.has(item))){
                        list.push(item);
                    }else if(type ===&#39;array&#39;){
                        add(item);
                    }
                });                
            },
            fire=function(data){
                fired=true;
                memory=options.memory&&data;  
                firingIndex=firingStart||0;             
                firingStart=0;//在memory模式下,add的时候firingStart可能会被置为其他值,这里将其还原,以备下次调用fire的时候从头开始执行
                var length;                    

                if(!list){                    return;
                }          
                for(length=list.length;firingIndex<length;firingIndex++){                    if(list[firingIndex].apply(data[0],data[1])===false&&options.stopOnFalse){                        break;
                    }
                }                // if(once){                //     if(memory){//如果通知是once和memory模式,那么在add的时候可以进行再次触发                //         list=[];                //     }else{//否则直接禁用                //         self.disable();                //     }                // }
            },
            self={
                add:function(){                    if(list){                        var start=list.length;
                        add(arguments);                        //如果是memory模式下的add,会导致立即触发
                        if(memory){//memory的初始值为undefined,memory模式下调用一次fire才会被赋值,因此第一次调用add的时候不会走下面
                            firingStart=start;
                            fire(memory);
                        }
                    }                    return this;
                },
                remove:function(){                    if(list){                        var i;
                        jQuery.each(arguments,function(i,item){                            //jQuery.inArray(item,list,i),返回item在list中的下表,从第i位向后数,包括第i为
                            while((i=jQuery.inArray(item,list,i))>-1){                                 
                                    list.splice(i,1);//删除上的数值                                                            }
                        });
                    }                    return this;
                },               
                //fn有值的时候,代表判断回调函数列表是否存在函数fn                //没有参数fn的时候,代表判断回调函数列表是否为空                has:function(fn){                    return fn?jQuery.inArray(fn,list)>-1:!!(list&&list.length);
                },                
                empty:function(){                    if(list){
                        list=[];
                    }                    return this;
                },
                disable:function(){                    //list就不用说了,list置为undefined之后,几乎所有的方法都不能调用                    //memory恢复初始值undefined
                    list=memory=undefined;                    return this;
                },
                disabled:function(){                    return !list;
                },
                fireWith:function(context,args){                    if(list&&!(once&&fired)){
                        args=args||[];//主要是为了处理args为undefined的情况
                        args=[context,args.slice?args.slice():args];
                        fire(args);                        
                    }                    return this;
                },
                fire:function(){
                    self.fireWith(this,arguments);                    return this;
                },
                fired:function(){                    return !!fired;
                },                //自己加的函数,供调试用                getList:function(){                    return list;
                }
            };            return self;
   };   //实现异步队列Defered,When   //异步队列内部维护了三个回调函数列表,分别是成功,失败,消息   jQuery.extend({        //func参数仅内部使用,func的调用者是jQuery.Deferred的返回值,参数也是        Deferred:function(func){            var doneList=jQuery.Callbacks('once memory'),
                failList=jQuery.Callbacks('once memory'),
                progressList=jQuery.Callbacks('memory'),
                state='pending',
                list={                    'resolve':doneList,                    'reject':failList,                    'notify':progressList
                },
                promise={
                    done:doneList.add,
                    fail:failList.add,
                    progress:progressList.add,                    
                    state:function(){                        return state;
                    },                    //同时添加成功,失败,消息回调函数                    then:function(doneCallback,failCallback,progressCallback){
                        deferred.done(doneCallback).fail(failCallback).progress(progressCallback);
                    },                    //成功,失败时,添加同一个处理函数                    always:function(){
                        deferred.done(arguments).fail(arguments);
                    },                    //说实话,能看懂这个源代码,但搞不太懂这个pipe是干嘛用的                    //实际使用中调用的地方也不多                    //不过其源代码有不少知识点值得学习                    pipe:function(fnDone,fnFail,fnProgress){                        //这里的newDefer,就是调用jQuery.Deferred(function(newDeferred))返回的异步队列对象,由这部分代码最终的func.apply(deferred,deferred)决定;
                        return jQuery.Deferred(function(newDefer){

                            jQuery.each({
                                done:[fnDone,'resolve'],
                                fail:[fnFail,'reject'],
                                progress:[fnProgress,'notify']                              
                            },function(handler,data){                                //注意这三个局部变量定义的位置,只能定义在该闭包中,如果定义在jQuery.Deferred得到的只是函数最后的值,如果没有传递fnProgress,就会报出undefined的错误
                                var action=data[1],
                                    fn=data[0],
                                    returned;                                if(jQuery.isFunction(fn)){                                    //通过done,fail,progress添加的方法,只有在对应的回调函数队列fire的时候才会触发                                    deferred[handler](function(){                                        //这里的this,arguments是调用fire/fireWith时候传递                                        //这里的this可以通过fireWith中指定context,arguments也是fire/fireWith的时候传递的参数                                        
                                        returned=fn.apply(this,arguments);                                        //如果函数的返回值依旧是一个异步队列,则将jQuery.pipe返回的异步队列的成功,失败,消息回调添加到返回的retuned对应的回调列表中
                                        if(returned&&jQuery.isFunction(returned.promise)){
                                            returned.promise().then(newDefer.resolve,newDefer.reject,newDefer.notify);
                                        }else{                                            //如果函数返回值不是异步队列,则jQuery.pipe()返回的异步队列对应状态的方法立即触发
                                            newDefer[action+'With'](this===deferred?newDefer:this,[returned]);
                                        }
                                    });
                                }else{
                                    deferred[handler](newDefer[action]);
                                }

                            });
                        }).promise();
                    },                    //注意promise()和promise({})这两种写法是完全不同的,前者返回异步对象的只读版本,后者返回一个副本                    promise:function(obj){                        return obj==null?promise:jQuery.extend(obj,promise);
                    },  
                },
                deferred=promise.promise({}),
                key;                //为deferred添加状态改变的相关函数,与fire,fireWith相对应
            for(key in list){
                deferred[key]=list[key].fire;
                deferred[key+'With']=list[key].fireWith;
            }
            deferred.done(function(){
                state='resolved';
            },failList.disable,progressList.disable)
            .fail(function(){
                state='rejected';
            },doneList.disable,progressList.disable);                
            if(func){                //这句话决定了,通过jQuery.Deferred(func)调用的时候,func的context和参数                func.call(deferred,deferred);
            }            return deferred;

        },
        When:function(firstParam){            var resolveArgs=core_slice.call(arguments,0),//用来存放成功参数
                length=resolveArgs.length,
                count=length,//维护一个计数器
                progressArgs=new Array(length),//用来存放消息参数
                i=0,                //只有当在只有一个参数,并且该参数是延迟对象的情况下,主延迟对象等于该第一个参数,否则新建一个主延迟对象
                deferred=length<=1&&firstParam&&jQuery.isFunction(firstParam.promise)?firstParam:jQuery.Deferred(),
                promise=deferred.promise();            if(length>1){                for(;i<length;i++){                    if(resolveArgs[i]&&jQuery.isFunction(resolveArgs[i].promise)){
                        resolveArgs[i].then(resolveFunc(i),deferred.reject,progressFunc(i));
                    }else{
                        count--;
                    }                    if(!count){
                        deferred.resolveWith(deferred,resolveArgs);
                    }
                }                
            }else if(deferred!==firstParam){//说明只有一个或0个参数,若有一个,该参数还不是延迟对象                //此时立即触发
                deferred.resolveWith(deferred,length?[firstParam]:[]);
            }            //为了将参数i的值传递,这里采用闭包            function resolveFunc(i){                //回调函数的参数(即返回函数中的value/arguments)是由fire/fireWith的时候进行参数指定
                return function(value){
                    resolveArgs[i]=arguments.length>1?core_slice.call(arguments):value;                    //每一次参数延迟对象的resolve触发,都令count的值减去一
                    if(!--count){                        //如果计算器变为0,那么主延迟对象的resolve方法触发                        deferred.resolveWith(deferred,resolveArgs);
                    }
                }
            }
            function progressFunc(i){                return function(value){
                    progressArgs[i]=arguments.length>1?core_slice.call(arguments):value;
                    
                    deferred.notifyWith(promise,progressArgs);
                }
            }            return promise;
        }
    
   });/*********数据缓存模块****************************************///数据缓存模块的整体思路//2.0.3版本的jQuery较之于1.7.3版本,使用面向对象的写法重构了数据缓存Data模块//数据缓存模块的整体依据是://data_user和data_priv在一次运行期间只有对应的唯一对象,所有DOM元素的缓存都基于这两个实例对象完成//data_user与data_priv这两个Data实例有各自的缓存对象属性cache,分别用于存储用户自定义数据和内部数据//以data_user为例,在向对应的data_user对应的缓存对象cache中保存数据时,会为每个DOM元素分配一个唯一的id,该id作为该DOM元素的附加属性//该唯一id(初始值为0,之后一次加1)会附加到DOM元素上,对应的DOM元素的属性名是data_user.expando,其对应的属性值就是id//同时,会把该id作为属性名添加到data_user的缓存对象属性cache中,对应的属性值是一个都object对象,该对象称为DOM元素的数据缓存对象,其中存储着属性名和属性值的映射//这样,通过分配唯一的id把DOM元素和该DOM元素的数据缓存对象关联起来//data_priv与之类似

    
    var data_priv,data_user,
        rbrace=/^(?:\{\s\S*\}|\[\s\S*\])$/,//匹配json字符串格式,诸如{},或者[],不用.*进行匹配的原因是.不能匹配换行符
        rmultiDash=/([A-Z])/g;//匹配任意的大写字母    

    function Data(){        //jQuery.expando是jQuery的静态属性,对于jQuery的每次加载运行期间时唯一的        //Math.random生成一个0-1之间的随机数
        this.expando=jQuery.expando+Math.random();        this.cache={};        //这里采用访问器属性的写法        //常用的写法是Object.defineProperty(对象,对象属性,{[[get]],[[set]],[[configurable]],})        //这句话的目的,this.cache中的0属性是个只读属性
        Object.defineProperty(this.cache,0,{            get:function(){                return {};
            }
        });
    }    //下面可以看到,只有当accepts为false的时候,返回的id为0
    Data.uid=1;
    Data.accepts=function(owner){        //只有DOM元素,document元素,以及普通的js对象可以操作数据缓存
        return owner.nodeType?owner.nodeType===1||owner.nodeType===9:true;
    };
    Data.prototype={        //获取(设置)owner对应的id,如果没有,则为其this.expando对应的属性,值为id,并未其在this.expando中创建缓存对象        key:function(owner){            if(!Data.accepts(owner)){                return 0;
            }            var expando=this.expando,
                id=owner[expando];            if(!id){
                id=Data.uid++;                //为owner定义expando属性,为了保证该属性不可遍历且只读,使用访问器属性进行定义                //defineProperty一次只定义一个属性,接受三个参数,对象,属性名,属性描述对象                //defineProperties可以通过描述符一次定义多个属性,接受两个参数                //具体用法可以参照讲解http://www.tuicool.com/articles/ju26riE                Object.defineProperty(owner,expando,{
                    value:id,
                }); 
            }            if(!this.cache[id]){                this.cache[id]={};
            }            return id;
        },        //为DOM元素对应的缓存设置数据        //data参数可以是字符串,也可以是对象/数组,当data是对象/数组的时候,value可以不赋值
        set:function(owner,data,value){            var id=this.key(owner),            //该DOM元素对应的缓存对象
                cache=this.cache[id],
                key;            if(typeof data==='string'){
                cache[data]=value;
            }else{                for(key in data){
                    cache[key]=data[key];
                }
            }            return cache;
        },        //获取DOM元素owner对应缓存中属性key的值        //如果参数key不赋值,则代表去除owner对应的对象缓存
        get:function(owner,key){            var id=owner[this.expando],
                cache;                if(!id){                    return undefined;
                }
                cache=this.cache[id];                return key?cache[key]:cache;
        },        //设置或获取        access:function(owner,key,value){            var tmp;            if(!key||((key&&typeof key==='string') &&!value)){//说明是获取                //先尝试key本身,不行的话尝试转驼峰
                tmp=this.get(owner,key);                return tmp? tmp: this.get(owner,jQuery.camelCase(key));
            }            //否则说明是设置
            this.set(owner,key ,value);            return value ? value : key;

        },        //如果没有传入参数key,则移除DOM元素或者javascript元素关联的所有数据        //如果传入了参数key,则移除关联的指定名称的数据        //如果key是数组或者空格分割的多个数据名,则一次可以删除多个数据,删除的时候还需要尝试camel转换之后的形式        remove:function(owner,key){            var i,camel,length,
                id=this.key(owner),
                cache=this.cache[id];            if(!key){                this.cache[id]={};
            }else{                 //可能是数组,可能是字符串,该字符串还可能使用空格分开
                 if(typeof key ==='string'){
                    key=key.match(rnotwhite);//转换为数组形式                 }
                 key=key.concat(jQuery.map(key,jQuery.camelCase));                 for(i=0,length=key.length;i<length;i++){
                    delete cache[key[i]];
                 }
            }        
        },        //返回owner对应的缓存对象是否有值        hasData:function(owner){             
            return !jQuery.isEmptyObject(this.cache[owner[this.expando]]||{});
        },        //删除Owner对应的缓存对象(注意不是讲缓存对象置为空数组)        discard:function(owner){            if(owner[this.expando]){
                delete this.cache[owner[this.expando]];
            }
        }
    };
    data_user=new Data();
    data_priv=new 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);
        },        
        //下面这两个get方法在jquery源代码中没有,这里加上,便于测试
        get:function(elem,key){            return data_user.get(elem,key);
        },
        _get:function(elem,key){            return data_priv.get(elem,key);
        }
    });    //这部分操作的都是用户自定义数据    jQuery.fn.extend({
        data:function(key,value){            //在这一步中,jQuery的源代码考虑了
            jQuery.each(this,function(){
                jQuery.data(this,key,value);                //console.log(jQuery.get(this));                //console.log($(this).getData());            });

        },
        removeData:function(key){            //别忘了,jquery对象是类数组
            jQuery.each(this,function(){            //    data_user.remove(this,key);
                jQuery.removeData(this,key);
            });
        },        //下面的getData方法也是为了测试方便加上的,在jQuery源代码中没有        getData:function(key){    
        //下面这种写法不对,注意结合jQuery.each源代码看就知道,get到的值并没有返回        
            // jQuery.each(this,function(){                
            //     jQuery.get(this,key);            // });            //根据jQuery的思路,获取的时候,获得的总是首个
            return jQuery.get(this[0],key);
        }
    });    //jQuery的源代码中用于处理html5中的data属性,这里暂不考虑    function dataAttr(elem,key,data){

    }    
    //搞清楚构造函数(包括普通函数),构造函数的原型,实例之间的关系    //javascript中每个函数都有一个prototype属性,指向其原型对象    //原型对象有一个constructor属性,指向对应的构造函数    //每个实例对象有一个隐形属性,在chrome中是_proto_,指向原型对象,注意这个属性是介于实例和原型对象之间的
    

    /************************jQuery队列Queue模块**********************************/
    //jQuery的队列模块基于数据缓存模块,异步回调和数组实现    //jQuery的队列为动画模块等提供基础功能,其队列中的元素都是函数;jQuery将其单独提取了一个命名空间,说明程序员也可以发挥自己的想法创建出非动画队列    //在队列模块中,通过数组存储函数,通过数组方法.push()和.shift()来实现入队和出队操作,通过.call来执行函数    //和普通队列不同的是,除了支持IFIO之外,出队的函数还可以自动调用,其调用的上下文是DOM元素elem,参数是next,数据缓存中的typeHooks对象,这些在dequeue中被指定    //对于动画队列,入队的动画函数会自动调用方法.dequeue()出队并且执行,对于非动画队列,则需要手动调用方法.dequeue()    //数组作为内部数据存储在关联的数据缓存对象,数据名称为队列名称加字符串“queue”    //如果没有传入队列名称,则默认为标准动画fx;    //队列模块会在队列名称后自动加上后缀queue,表示这是一个队列    //这部分的数据缓存对象结构是:data-priv——>DOM元素的连续数字ID——>1)typequeue:[队列函数列表],    //2)typequeueHooks:A)empty,对应一个callbacks回调函数,该回调函数在队列函数列表执行完毕的时候被调用,执行的操作包括:a)清楚缓存typequeue和typequeueHooks;    //b)如果还调用了promise,则为hooks的empty对应的回调函数callbacks添加了令监控计数器count减1的操作;    //c)如果还调用了delayed,回味typeHooks添加stop属性,用于终止timeout延时计算器    

    //队列模块的代码结构        jQuery.extend({        //该方法用于返回或者修改匹配元素关联的函数队列,根据传入参数的不同,函数实现的功能也有所不同        //这是一个低级方法,外部调用的时,应该用.queue替换        //queue(elem,[type])返回匹配元素关联的函数队列        //queue(elem,type,newQueue)参数data是函数数组,此时用newQueue替换当前队列        //queue(elem,type,callback())参数data是函数,此时将callback添加到当前的函数队列中        queue:function(elem,type,data){            var type=type||'fx'+queue,
                queue=data_priv.get(elem,type);            if(data){              //说明是设置操作
              if(!queue||jQuery.isArray(data)){                //必须使用jQuery.makeArray,针对!queue且data是function的情况                data_priv.access(elem,type,jQuery.makeArray(data));
              }else{
                queue.push(data);
              }
            }            return queue||[];
        },        //jQuery中的队列不同于队列定义的是,jQuery的队列不仅支持函数的入队和出队操作,出队的函数还会自动调用        dequeue:function(elem,type){             
            var type=type||'fx',
                queue=jQuery.queue(elem,type),
                hooks=jQuery._queueHooks(elem,type),
                startLength=queue.length,
                fn=queue.shift(),                //不能令next=jQuery.dequeue,因为不能指定参数啊啊啊
                next=function(){
                    jQuery.dequeue(elem,type);
                };          
            //这个inprogress搞不太懂,回头结合动画effects模块一起看吧
            if(fn==='inprogress'){
                fn=queue.shift();
                startLength--;
            }            if(fn){                //同样,inprogress搞不懂,看动画模块如何让inprogress出队吧
                if(type==='fx'){
                    queue.unshift('inprogress');
                }                //取消hooks上的定时器,这个依旧搞不太懂,结合delay一起看吧                delete hooks.stop;                //先不考虑动画和延时                
                fn.call(elem,next,hooks);
            }            //注意上面fn.call之后startLength并没有-1            //测试的结果是,只有在队列已经为空的情况下,再次调用dequeue进行出队,才会触发缓存清除的empty操作
            if(!startLength&&hooks){
               hooks.empty.fire();
            }

        },
        _queueHooks:function(elem,type){            var hookKey=type+'queueHooks';            return data_priv.get(elem,hookKey)||data_priv.access(elem,hookKey,{
                empty:jQuery.Callbacks('once memory').add(function(){
                    data_priv.remove(elem,[type+'queue',hookKey]);
                    console.log('empty call');
                })
            });            
        }
    });
    jQuery.fn.extend({        //.queue([queuename]);返回第一个匹配元素关联的函数队列        //.queue([queueName],newQueue);修改匹配元素关联的函数队列,使用函数数组newQueue替换当前队列        //.queue([queueName],callback(next,hooks));修改匹配元素关联的函数队列,添加callback到队列中        //如果queueName省略,则默认是动画队列fx        queue:function(type,data){            var setter=2;            if(typeof type!=='string'){                //进行参数修正
                data=type;
                type='fx';
                setter--;
            }            //靠,这种判断是获取还是设置的点子是怎么想出来的
            if(arguments.length<setter){                //说明是获取操作,根据jQuery的思想,获取的时候,仅获取首个
                return jQuery.queue(this[0],type);
             }             //否则说明是设置操作,根据jQuery的思想,设置的时候,进行遍历设置
             if(data){                this.each(function(i,item){                    var queue=jQuery.queue(this,type,data);                    //确定添加了hooks
                    jQuery._queueHooks(this,type);                    //如果是动画队列,那么首次入队的时候回自动出队执行,不必手动调用dequeue,唉,这点结合动画模块来看吧
                    if(type==='fx'&&queue[0]!=='inprogress'){
                        jQuery.dequeue(this,type);
                    }
                });
             }

        },
        dequeue:function(type){            this.each(function(){
                jQuery.dequeue(this,type);
            });
        },        //使得队列中下一个函数延迟执行        delay:function(time,type){
            time=jQuery.fx?jQuery.fx.speeds[time]||time:time;
            type=type||'fx';            //next和hooks的参数赋值是在dequeue的fn.call中,还记得么?
            return this.queue(type,function(next,hooks){                var timerId=setTimeout(next,time);
                hooks.stop=function(){
                    clearTimeout(timerId);
                }
            });
        },
        clearQueue:function(type){            this.queue(type||'fx',[]);
        },        //针对每一个匹配元素,对其添加监控,当所有匹配元素的type队列中的函数都执行完毕时,调用Promise的done添加的成功回调函数        promise:function(type,obj){            var elems=this,
                count=0,
                i=elems.length,
                defered=jQuery.Deferred(),
                hook;            if(typeof type!=='string'){
                obj=type;
                type=undefined;
            }
            type=type||'fx';
            function resolve(){                if(!(--count)){                    //如果计数器count变为0                    defered.resolveWith(elems,[elems]);
                }
            }            //添加监控
            while(i--){
                hook=elems[i]&&data_priv.get(elems[i],type+'queueHooks');                if(hook&&hook.empty){
                    count++;
                    hook.empty.add(resolve);
                }
            }            //这里为毛要调用一次呢?           // resolve();
            return defered.promise(obj);

        }
    });    





    window.jQuery=window.$=jQuery;
 
})(window);

The above is the detailed content of jQuery source code learning queue module queue. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn