深入理解jQuery中的callback

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入理解jQuery中的callback相关的知识,希望对你有一定的参考价值。

本文要讲的是jQuery.Callback() ,这是一个构造函数。在讲解这个之前,我们需要先讲讲javascript中的回调函数。

什么是回调?

  所谓回调函数,我的理解:回头再调用,即:不是立刻运行,而是在特定时刻触发调用。

js中的同步和异步

在javascript中,回调函数和同步 异步有很深的渊源,所以我们先来理解一下在js中的同步和异步的概念:

  同步: 后一个任务等待前一个任务结束,然后再执行,程序的执行顺序与任务的排列顺序是一致的、同步的。

  异步:,每一个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的。

更确切的说:回调和异步有很深的渊源,js中的  事件  定时器  ajax  animate等等都用到了回调函数。

 

为什么jQuery搞了个Callback()?

我们上面说到:js中的  事件  定时器  ajax  animate等等都用到了回调函数,而且回调函数可能有多个。

我们来举个例子:

function a(arg){
console.log(‘吃饭‘ + arg)
}
function b(arg){
console.log(‘睡觉‘ + arg)
}
function c(arg){
console.log(‘打豆豆‘ + arg)
}

var xiaoming = setTimeout(function(){
a(‘ing‘);
b(‘ing‘);
c(‘ing‘);
},1000);
console.log(‘------------‘)

  我们在这里用定时器函数实现了异步,在一秒钟后(这句话并不准确,明白为啥不准确的同学们就不要在意这句话了,我是为了方便,不明白的请自行查阅setTimeout函数详解)运行a b c三个函数。

  但是这里有个问题,a b c三个函数和小明耦合是不是有些深? 小明现在每天是吃饭睡觉打豆豆,如果某天他不仅要吃饭睡觉打豆豆,还要学习的话,我们还得修改xiaoming的内部结构。 所以我们改进一下上面的格式:

    function a(arg){
        console.log(‘吃饭‘ + arg)
    }
    function b(arg){
        console.log(‘睡觉‘ + arg)
    }
    function c(arg){
        console.log(‘打豆豆‘ + arg)
    }
    function d(arg){
        console.log(‘学习‘ + arg)
    }

    function callback(){
        var list = [];
        var self = {};
        self.add = function(thing){
            list.push(thing)
        };
        self.fire = function(arg){
            for(var i in list){
                list[i](arg)
            }
        };
        return self
    }

    var xiaomingDo = callback();
    xiaomingDo.add(a);
    xiaomingDo.add(b);
    xiaomingDo.add(c);
    xiaomingDo.add(d);

    var xiaoming = setTimeout(function(){
        xiaomingDo.fire(‘ing‘);     // 弱化耦合
    },1000);
console.log(
‘------------‘)

这样我们就弱化了耦合,将每天具体做的事从xiaoming中分离。

上例中的callback函数建立了一个用来保存函数的数组,最终返回了一个对象,这个对象具有两个方法:add 用来向数组中添加函数 ; fire 用来将数组中的函数全部执行。看到这里是不是感觉很熟悉? 没错,这里采用了观察者模式。

再来个小红:

    var xiaohongDo = callback();
    xiaohongDo.add(a);
    xiaohongDo.add(b);
    var xiaohong = setTimeout(function(){
        xiaohongDo.fire()
    },1000);

是不是感觉逻辑清晰很多。本例中的callback函数与jQuery中的Callback函数的作用以及实现原理是类似的。

jQuery.Callback()大体原理:

  jQuery.Callback()需要在内部维护着一个存储函数的队列数组。

  每当我们调用Callback时,都会返回一个新的对象。

  使用这个对象的方法我们可以对队列数组进行操作。

 

当然jQuery.Callback()并不是我们想象的那么简单,他还提供了很多可供选择的参数:

  once:保证数组内的函数只执行一遍

  memory:保持以前的fire参数值,每当添加一个新的函数时,会将之前的fire参数值作为参数立即调用新函数

  unique:确保数组中的函数无重复

  stopOnFalse:当一个函数返回false时中断

 源码:

jQuery.Callbacks = function( options ) {

    // Convert options from String-formatted to Object-formatted if needed
    // (we check in cache first)
    //通过字符串在optionsCache寻找有没有相应缓存,如果没有则创建一个,有则引用
    //如果是对象则通过jQuery.extend深复制后赋给options。
    options = typeof options === "string" ?
        ( optionsCache[ options ] || createOptions( options ) ) :
        jQuery.extend( {}, options );

    var // Last fire value (for non-forgettable lists)
        memory, // 最后一次触发回调时传的参数

        // Flag to know if list was already fired
        fired, // 列表中的函数是否已经回调至少一次

        // Flag to know if list is currently firing
        firing,  // 列表中的函数是否正在回调中

        // First callback to fire (used internally by add and fireWith)
        firingStart, // 回调的起点

        // End of the loop when firing
        firingLength, // 回调时的循环结尾

        // Index of currently firing callback (modified by remove if needed)
        firingIndex, // 当前正在回调的函数索引

        // Actual callback list
        list = [], // 回调函数列表

        // Stack of fire calls for repeatable lists
        stack = !options.once && [],// 可重复的回调函数堆栈,用于控制触发回调时的参数列表

        // Fire callbacks// 触发回调函数列表
        fire = function( data ) {
            //如果参数memory为true,则记录data
            memory = options.memory && data;
            fired = true; //标记触发回调
            firingIndex = firingStart || 0;
            firingStart = 0;
            firingLength = list.length;
            //标记正在触发回调
            firing = true;
            for ( ; list && firingIndex < firingLength; firingIndex++ ) {
                if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
                    // 阻止未来可能由于add所产生的回调
                    memory = false; // To prevent further calls using add
                    break; //由于参数stopOnFalse为true,所以当有回调函数返回值为false时退出循环
                }
            }
            //标记回调结束
            firing = false;
            if ( list ) {
                if ( stack ) {
                    if ( stack.length ) {
                        //从堆栈头部取出,递归fire
                        fire( stack.shift() );
                    }
                } else if ( memory ) {//否则,如果有记忆
                    list = [];
                } else {//再否则阻止回调列表中的回调
                    self.disable();
                }
            }
        },
        // Actual Callbacks object
        // 暴露在外的Callbacks对象,对外接口
        self = {
            // Add a callback or a collection of callbacks to the list
            add: function() { // 回调列表中添加一个回调或回调的集合。
                if ( list ) {
                    // First, we save the current length
                    //首先我们存储当前列表长度
                    var start = list.length;
                    (function add( args ) { //jQuery.each,对args传进来的列表的每一个对象执行操作
                        jQuery.each( args, function( _, arg ) {
                            var type = jQuery.type( arg );
                            if ( type === "function" ) {
                                if ( !options.unique || !self.has( arg ) ) { //确保是否可以重复
                                    list.push( arg );
                                }
                            //如果是类数组或对象,递归
                            } else if ( arg && arg.length && type !== "string" ) {
                                // Inspect recursively
                                add( arg );
                            }
                        });
                    })( arguments );
                    // Do we need to add the callbacks to the
                    // current firing batch?
                    // 如果回调列表中的回调正在执行时,其中的一个回调函数执行了Callbacks.add操作
                    // 上句话可以简称:如果在执行Callbacks.add操作的状态为firing时
                    // 那么需要更新firingLength值
                    if ( firing ) {
                        firingLength = list.length;
                    // With memory, if we‘re not firing then
                    // we should call right away
                    } else if ( memory ) {
                        //如果options.memory为true,则将memory做为参数,应用最近增加的回调函数
                        firingStart = start;
                        fire( memory );
                    }
                }
                return this;
            },
            // Remove a callback from the list
            // 从函数列表中删除函数(集)
            remove: function() {
                if ( list ) {
                    jQuery.each( arguments, function( _, arg ) {
                        var index;
                        // while循环的意义在于借助于强大的jQuery.inArray删除函数列表中相同的函数引用(没有设置unique的情况)
                        // jQuery.inArray将每次返回查找到的元素的index作为自己的第三个参数继续进行查找,直到函数列表的尽头
                        // splice删除数组元素,修改数组的结构
                        while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
                            list.splice( index, 1 );
                            // Handle firing indexes
                            // 在函数列表处于firing状态时,最主要的就是维护firingLength和firgingIndex这两个值
                            // 保证fire时函数列表中的函数能够被正确执行(fire中的for循环需要这两个值
                            if ( firing ) {
                                if ( index <= firingLength ) {
                                    firingLength--;
                                }
                                if ( index <= firingIndex ) {
                                    firingIndex--;
                                }
                            }
                        }
                    });
                }
                return this;
            },
            // Check if a given callback is in the list.
            // If no argument is given, return whether or not list has callbacks attached
            // 回调函数是否在列表中.
            has: function( fn ) {
                return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );
            },
            // Remove all callbacks from the list
            // 从列表中删除所有回调函数
            empty: function() {
                list = [];
                firingLength = 0;
                return this;
            },
            // Have the list do nothing anymore
            // 禁用回调列表中的回调。
            disable: function() {
                list = stack = memory = undefined;
                return this;
            },
            // Is it disabled?
            //  列表中否被禁用
            disabled: function() {
                return !list;
            },
            // Lock the list in its current state
            // 锁定列表
            lock: function() {
                stack = undefined;
                if ( !memory ) {
                    self.disable();
                }
                return this;
            },
            // Is it locked?
            // 列表是否被锁
            locked: function() {
                return !stack;
            },
            // Call all callbacks with the given context and arguments
            // 以给定的上下文和参数调用所有回调函数
            fireWith: function( context, args ) {
                if ( list && ( !fired || stack ) ) {
                    args = args || [];
                    args = [ context, args.slice ? args.slice() : args ];
                    //如果正在回调
                    if ( firing ) {
                        //将参数推入堆栈,等待当前回调结束再调用
                        stack.push( args );
                    } else {//否则直接调用
                        fire( args );
                    }
                }
                return this;
            },
            // Call all the callbacks with the given arguments
            // 以给定的参数调用所有回调函数
            fire: function() {
                self.fireWith( this, arguments );
                return this;
            },
            // To know if the callbacks have already been called at least once
            // // 回调函数列表是否至少被调用一次
            fired: function() {
                return !!fired;
            }
        };
    return self;
};
复制代码

本文参考:http://www.cnblogs.com/aaronjs/p/3342344.html

 

以上是关于深入理解jQuery中的callback的主要内容,如果未能解决你的问题,请参考以下文章

深入理解jQuery中的Deferred

深入理解jQuery中的Deferred

深入理解jQuery中的Deferred

深入理解jQueryAngularnode中的Promise

jQuery-异步请求

深入理解jQuery中的ajax