美文网首页我爱编程
jQuery-v2.0.3源码浅析03-Callbacks

jQuery-v2.0.3源码浅析03-Callbacks

作者: 赠前端 | 来源:发表于2018-03-27 09:33 被阅读0次

    废话不多说我们今天来看下jQuery的Callbacks函数。
    在看Callbacks源码之前,我们先来看看Callbacks的简单使用吧。
    Callbacks用来统一管理函数的。

    function aaa(){
        console.log(1);
    }
    function bbb(){
        console.log(2);
    }
    var cb= $.Callbacks();
    cb.add(aaa);
    cb.add(bbb);
    cb.fire();
    

    以上代码执行完毕之后,在控制台会依次输出1和2。
    如果用传统的方式

    function aaa(){
        console.log(1);
    }
    function bbb(){
        console.log(2);
    }
    aaa();
    bbb();
    

    对比之后可以看出最终函数的执行callbacks只需要调用fire就可以依次执行add添加的函数了。
    当然这只是callbacks最简单的使用。

    我们再来看一个例子

    function aaa(){
      console.log(1);
    }
    (function(){
      function bbb(){
        console.log(2);
      }
    })();
    aaa();
    bbb();
    

    这个时候会发现bbb根本找不到,因为bbb函数定义在子作用域里面
    我们再来看看callbacks的写法

    var cb= $.Callbacks();
    function aaa(){
        console.log(1);
    }
    cb.add(aaa);
    (function(){
        function bbb(){
            console.log(2);
        }
        cb.add(bbb);
    })();
    cb.fire();
    

    我们发现只要我们把cb定义在全局就可以解决这种作用域问题,是不是很方便。

    当然再看源码之前我们可以自己模拟一下这个callbacks函数

    function callbacks(){
        var list = [],
            self = {
                add: function(fn){
                    list.push(fn);
                },
                fire: function(){
                    for(var i = 0; i < list.length; ++i){
                        var item = list[i];
                        item && item();
                    }
                }
            }
        return self;
    }
    function a(){
        console.log(1);
    }
    function b(){
        console.log(2);
    }
    var cb = callbacks();
    cb.add(a);
    cb.add(b);
    cb.fire();
    

    我们可以看到控制台依次输出了1和2。
    其实Callbacks最基础的实现部分就是这样的。

    让我们再看看Callbacks的参数吧,Callbacks可以接受'once'、'memory'、'unique'、'stopOnFalse'这种类型,还可以4种类型随便组合用空格隔开例如'once memory'。

    先看once参数吧,从单词意思可以看出来这个参数的意思就是让add的方法只执行一次,例如:

    function a(){
        console.log(1);
    }
    function b(){
        console.log(2);
    }
    var cb = $.Callbacks();
    cb.add(a);
    cb.add(b);
    cb.fire();
    cb.fire();
    //如果不传参数控制台会输出1、2、1、2 ,如果这样调用
    var cb = $.Callbacks(‘once’);
    //会发现控制台只会输出一遍1、2
    

    这个时候我们可以尝试修改自己的callbacks函数来实现这个效果

    function createOptions(options){
        var object = {};
        var list = options.split(' ') || [];
        for(let i = 0; i < list.length; ++i){
            object[list[i]] = true;
        }
        return object;
    }
    function callbacks( options ){
        //转换一下参数方便判断
        options = createOptions(options);
    
        var firingIndex,
            firingLength,
            list = [],
            self = {
                add: function(fn){
                    list.push(fn);
                },
                fire: function(){
                    firingLength = list.length;
                    if(!options.once || options.once && firingIndex == null) firingIndex = 0;
    
                    for(;list && firingIndex < firingLength; ++firingIndex){
                        var item = list[firingIndex];
                        item && item();
                    }
                }
            }
        return self;
    }
    function a(){
        console.log(1);
    }
    function b(){
        console.log(2);
    }
    var cb = callbacks('once');
    cb.add(a);
    cb.add(b);
    cb.fire();
    cb.fire();
    

    然后我们再来看一下比较简单的一个参数'stopOnFalse',stopOnFalse的作用就是当list里面函数执行完毕之后如果返回false就停止执行,例如:

    function a(){
        console.log(1);
        return false;
    }
    function b(){
        console.log(2);
    }
    var cb = $.Callbacks('stopOnFalse');
    cb.add(a);
    cb.add(b);
    cb.fire();
    

    控制台只会输出1

    我们也可以改造一下我们的代码来实现这个效果

    function createOptions(options){
        var object = {};
        var list = options.split(' ') || [];
        for(let i = 0; i < list.length; ++i){
            object[list[i]] = true;
        }
        return object;
    }
    function callbacks( options ){
        //转换一下参数方便判断
        options = createOptions(options);
    
        var firingIndex,
            firingLength,
            list = [],
            self = {
                add: function(fn){
                    list.push(fn);
                },
                fire: function(){
                    firingLength = list.length;
                    if(!options.once || options.once && firingIndex == null) firingIndex = 0;
    
                    for(;list && firingIndex < firingLength; ++firingIndex){
                        var item = list[firingIndex];
                        if(item && item() === false && options.stopOnFalse){
                            break;
                        }
                    }
                }
            }
        return self;
    }
    function a(){
        console.log(1);
        return false;
    }
    function b(){
        console.log(2);
    }
    var cb = callbacks('stopOnFalse');
    cb.add(a);
    cb.add(b);
    cb.fire();
    

    接下来我们看看'memory'的用法,memory的作用是当fire方法调用之后,我们再使用add添加新的函数的时候直接执行。

    function a(){
        console.log(1);
    }
    function b(){
        console.log(2);
    }
    var cb = $.Callbacks();
    cb.add(a);
    cb.fire();
    cb.add(b);
    //发现控制台只会打印1
    //接下来我们来改一下参数
    var cb = $.Callbacks('memory');
    //这个时候控制台会输出1和2
    

    这个时候按照我们自己的思路来设计的话处理逻辑是不是应该放在add方法中,判断一下当前状态如果已经fire过的话,就直接执行,
    我们接着来改造一下我们的代码

    function createOptions(options){
        var object = {};
        var list = options.split(' ') || [];
        for(let i = 0; i < list.length; ++i){
            object[list[i]] = true;
        }
        return object;
    }
    function callbacks( options ){
        //转换一下参数方便判断
        options = createOptions(options);
    
        var firingIndex,
            firingLength,
            list = [],
            fired,
            self = {
                add: function(fn){
                    if(fired){
                        fn && fn();
                    }else{
                        list.push(fn);
                    }
                },
                fire: function(){
                    fired = true;
                    firingLength = list.length;
                    if(!options.once || options.once && firingIndex == null) firingIndex = 0;
    
                    for(;list && firingIndex < firingLength; ++firingIndex){
                        var item = list[firingIndex];
                        if(item && item() === false && options.stopOnFalse){
                            break;
                        }
                    }
                }
            }
        return self;
    }
    function a(){
        console.log(1);
    }
    function b(){
        console.log(2);
    }
    var cb = callbacks('memory');
    cb.add(a);
    cb.fire();
    cb.add(b);
    

    接下来我们再看一下'unique'参数,unique参数的作用就是控制add进来的函数不能有重复的。例如:

    function a(){
        console.log(1);
    }
    var cb = $.Callbacks();
    cb.add(a);
    cb.add(a);
    cb.fire();
    //执行完毕之后控制台会输出两个1
    //我们来改一下参数
    var cb = $.Callbacks('unique');
    //控制台只会输出一个1
    

    我们来该找一下自己的callbacks吧

    function createOptions(options){
        var object = {};
        var list = options.split(' ') || [];
        for(let i = 0; i < list.length; ++i){
            object[list[i]] = true;
        }
        return object;
    }
    function callbacks( options ){
        //转换一下参数方便判断
        options = createOptions(options);
    
        var firingIndex,
            firingLength,
            list = [],
            fired,
            self = {
                add: function(fn){
                    if(fired){
                        fn && fn();
                    }else{
                        if(options.unique && list.indexOf(fn) > -1){
                            return;
                        }
                        list.push(fn);
                    }
                },
                fire: function(){
                    fired = true;
                    firingLength = list.length;
                    if(!options.once || options.once && firingIndex == null) firingIndex = 0;
    
                    for(;list && firingIndex < firingLength; ++firingIndex){
                        var item = list[firingIndex];
                        if(item && item() === false && options.stopOnFalse){
                            break;
                        }
                    }
                }
            }
        return self;
    }
    function a(){
        console.log(1);
    }
    var cb = callbacks('unique');
    cb.add(a);
    cb.add(a);
    cb.fire();
    

    上面我们对callbacks进行了简单实现,但还有非常多的情况没有判断,我们来看一下jQuery是如何处理的吧。
    源码注释版

    //缓存options下次如果又传了同样的参数就不需要重新计算一边了,提高性能类似 {'once memory': { 'once': true, 'memory': true }}
    var optionsCache = {};
    // 将字符串类型的options转换成对象,并且缓存起来
    function createOptions( options ) {
        var object = optionsCache[ options ] = {};
        jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) {
            object[ flag ] = true;
        });
        return object;
    }
    jQuery.Callbacks = function( options ) {
    
        //转换options对象,options还可以接收类似 { 'once': true, 'memory': true } 的对象
        options = typeof options === "string" ?
            ( optionsCache[ options ] || createOptions( options ) ) :
            jQuery.extend( {}, options );
    
        var memory,
            fired,
            firing,
            firingStart,
            firingLength,
            firingIndex,
            list = [],
            stack = !options.once && [],//如果设置了once则stack=false,如果没有设置once则stack=[]可以存储后续操作
            fire = function( data ) {
                memory = options.memory && data;//如果设置了memory则memory = data,否则memory=false
                fired = true;//如果执行过fire函数就把fired设置成true,代表已经fire过了
                firingIndex = firingStart || 0;
                firingStart = 0;
                firingLength = list.length;
                firing = true;//fire过程中记录一下状态
                for ( ; list && firingIndex < firingLength; firingIndex++ ) {
                    if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {//如果函数执行完毕之后返回false并且设置了stopOnFalse,直接跳出循环
                        memory = false; 
                        break;
                    }
                }
                firing = false;//fire完毕把状态改回来
                if ( list ) {//如果这个Callbacks没有销毁
                    if ( stack ) {//在存在在fire过程中调用fire,在fire完毕之后再执行
                        if ( stack.length ) {
                            fire( stack.shift() );
                        }
                    } else if ( memory ) {//设置了once又设置了memory才会执行将list清空确保不重复执行
                        list = [];
                    } else {//如果设置once,没有设置memory 则在一次执行完毕之后 销毁Callbacks
                        self.disable();
                    }
                }
            },
            self = {
                add: function() {
                    if ( list ) {
                        var start = list.length;//记录list下标,在使用'memory'参数的时候使用
                        (function add( args ) {//循环push函数
                            jQuery.each( args, function( _, arg ) {
                                var type = jQuery.type( arg );
                                if ( type === "function" ) {
                                    if ( !options.unique || !self.has( arg ) ) {//如果使用了'unique'并且添加重复函数会直接忽略
                                        list.push( arg );
                                    }
                                } else if ( arg && arg.length && type !== "string" ) {//add也可以接收一个数组或者类似数组
                                    add( arg );
                                }
                            });
                        })( arguments );
                        if ( firing ) {
                            /**
                                考虑到在fire过程中,发现函数里面调用了add方法,这个时候需要更新一下循环长度
                                例如:
                                function a(){
                                    console.log(1);
                                    funciton b(){
                                        console.log(2);
                                    }
                                    cb.add(b);
                                }
                            **/
                            firingLength = list.length;
                        } else if ( memory ) {//如果fire过一次并且设置了memory
                            firingStart = start;
                            fire( memory );
                        }
                    }
                    return this;
                },
                remove: function() {//移除list中的某一函数
                    if ( list ) {
                        jQuery.each( arguments, function( _, arg ) {
                            var index;
                            while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
                                list.splice( index, 1 );
                                if ( firing ) {//判断一下是否在fire过程中进行了remove
                                    if ( index <= firingLength ) {
                                        firingLength--;
                                    }
                                    if ( index <= firingIndex ) {
                                        firingIndex--;
                                    }
                                }
                            }
                        });
                    }
                    return this;
                },
                has: function( fn ) {//判断数组中是否包含fn
                    return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );
                },
                empty: function() {//清空list,可以重新添加调用
                    list = [];
                    firingLength = 0;
                    return this;
                },
                disable: function() {//销毁掉这个Callbacks,不可进行其他操作
                    list = stack = memory = undefined;
                    return this;
                },
                disabled: function() {//判断是否被销毁
                    return !list;
                },
                lock: function() {//锁住
                    stack = undefined;
                    if ( !memory ) {
                        self.disable();
                    }
                    return this;
                },
                locked: function() {//判断是否锁住了
                    return !stack;
                },
                fireWith: function( context, args ) {//处理一下参数,可以设置回调函数的最终context
                    if ( list && ( !fired || stack ) ) {
                        args = args || [];
                        args = [ context, args.slice ? args.slice() : args ];
                        if ( firing ) {//如果fire过程中又调用了fire先存储起来
                            stack.push( args );
                        } else {//调用fire函数,并传入参数
                            fire( args );
                        }
                    }
                    return this;
                },
                fire: function() {
                    self.fireWith( this, arguments );
                    return this;
                },
                fired: function() {//判断是否fire过
                    return !!fired;
                }
            };
    
        return self;
    };
    

    相关文章

      网友评论

        本文标题:jQuery-v2.0.3源码浅析03-Callbacks

        本文链接:https://www.haomeiwen.com/subject/xzyrcftx.html