首页 > 代码库 > 深入理解jQuery中的callback

深入理解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