jQuery源代码学习之四——jQuery.callbacks

jQuery的callbacks模块事实上是发布-订阅模式的一种实现,但可以通过以下参数对回调函数列表实现更多控制。包括:

once: 回调函数队列只能被执行一次(实现原理是判断回调函数队列是否被执行过,如果执行过,则不再执行)

unique: 回调函数不能重复添加到队列(实现原理是add的时候,判断现有队列是否已有相同函数,如果有则不再添加)

stopOnFalse: 回调函数队列中的函数是依次执行的,当某个函数的返回值为false的时候,停止后续函数执行(实现原理是fire的时候,如果某个函数返回值为true,则退出循环)

memory:当函数队列fire或者fireWith一次的时候(传递给fire的参数会传递给各个回调函数),内部会记录fire或者fireWith的参数,当下次add的时候,会将记录的参数传递给新添加的函数并立即执行这个新添加的函数(基本原理是使用memory参数记录每次fire时传递的参数,使用fireStart记录本次触发应当开始的下标)。

jQuery的Defered模式,就使用了once和memory的组合。进一步,我们知道Defered是ES6 promise的一种实现,可想promise也是基于类似的原理。

模块的具体用法可以参见这篇博客:https://segmentfault.com/a/1190000004331027

 

自己实现的callbacks模块相较于jquery源代码中的callbacks模块有所简化,表面上看没有考虑firing这个参数,没有对之进行任何处理,即没有考虑在函数执行过程中,再次调用add,remove,fire的种种情况。

话不多少,直接上代码,所有的思考都在代码里面:

这其中有几个关键的变量,控制回调函数列表执行的流程,细细体会其中的思想:

fired,

firingStart,

 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==='string'?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==='function'&&!(options.unique&&self.has(item))){
                        list.push(item);
                    }else if(type ==='array'){
                        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;
                }
            };
            return self;
   };

截止目前,当前版本的myJquery.js的全部代码:

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

        class2type={},
        core_deletedIds=[],
        core_version='2.0.3',

        _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:'',//含有连续的整型属性、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 ==='string'){
                match=idExpr.exec(selector);
                if(match&&!context){
                    elem=document.getElementById(match[1]);
                    if(elem&&elem.parentNode){
                        this[0]=elem;
                        this.length=1;                        
                    }
                    this.selector=selector;
                    this.context=document;
                    return this;
                }else{
                    //说明是复杂的选择器表达式,这里暂不考虑
                }                
           }
           //处理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==='boolean'){
            deep=target;
            target=arguments[1]||{};
            i=2;
        }
        //此时target就是jQuery或jQuery.fn
        if(i===length){
            target=this;
            i--;
        }
        //处理target是字符串或者其他情形,这在深度复制中可能出现
        // if(typeof target!=='object'||!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==='array'){
            return true;
        }
        if(typeof length==='number'&&(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?'':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=='string' || type=='function' ||type=='regexp'||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=='number'){
                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==='string'?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==='function'&&!(options.unique&&self.has(item))){
                        list.push(item);
                    }else if(type ==='array'){
                        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;
                }
            };
            return self;
   };


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

 

posted @ 2016-10-19 21:39  bobo的学习笔记  阅读(473)  评论(0编辑  收藏  举报