前言:

作为参数传递给另一个函数执行的函数我们称为回调函数,那么该回调又是否是异步的呢,何谓异步,如:作为事件处理器,或作为参数传递给

(setTimeout,setInterval)这样的异步函数,或作为ajax发送请求,应用于请求各种状态的处理,我们可以称为异步回调,jQuery.Callbacks

为我们封装了一个回调对象模块,我们先来看一个应用场景:

// 为什么jQuery中的ready事件可以执行多个回调,这得益于我们的jQuery.Deferred递延对象(是基于jQuery.Callbacks回调模块)
jQuery(function($) {
    console.log('document is ready!');
    // do something
});  

jQuery(function($) {
    // do something
});  

// 实现原型
// jQuery.Deferred版代码
var df = jQuery.Deferred();
df.resolve(); // 在ready事件中调用  

// 可以多次执行绑定的函数
df.done(fn1, fn2, fn3);
df.done(fn4);
// ...  

// jQuery.Callbacks版代码
var cb = jQuery.Callbacks('once memory');
cb.fire(); // 在ready事件中调用  

// 可以多次执行绑定的函数
cb.add(fn1, fn2, fn3);
cb.add(fn4);
// ...
现在我们知道jQuery中的ready事件是可以这样执行多个回调的,要想深入理解其源码,让我们继续看下面吧

一、jQuery.Callbacks设计思路

使用一个私有变量list(数组)存储回调,执行jQuery.Callbacks函数将返回一个可以操作回调列表list的接口对象,
而传入jQuery.Callbacks函数的options参数则用来控制返回的回调对象操作回调列表的行为

回调对象中的方法

{
    add:        增加回调到list中
    remove:     从list中移除回调
    fire:       触发list中的回调
    fired:      回调对象是否执行过fire方法
    fireWith:   触发list中的回调,第一个参数为执行域
    has:        判断函数是否在list中
    empty:      将list致空,list = [];
    lock:       锁定list
    locked:     是否锁定
    disable:    禁用回调对象
    disabled:   是否禁用
}
参数标志:

options = {
    once:       回调对象仅触发(fire)一次  

    memory:     跟踪记录每一次传递给fire函数的参数,在回调对象触发后(fired),
                将最后一次触发(fire)时的参数(value)传递给在add操作后即将被调用的回调  

    unique:     在add操作中,相同的函数仅只一次被添加(push)到回调列表中  

    stopOnFalse:当回调函数返回false,中断列表中的回调循环调用,且memory === false,阻止在add操作中将要触发的回调
} 
二、源码解析

    var // Used for splitting on whitespace
        core_rnotwhite = /\S+/g;  

    var optionsCache = {};  

    // Convert String-formatted options into Object-formatted ones and store in cache
    // 将字符串格式选项转化成对象格式形式,并存储在缓存对象optionsCache[options]中
    // 该缓存起作用适用于执行多次jQuery.Callbacks函数,且传递options参数一致,我们在jQuery.Deferred
    // 源码就可以看到tuples二维数组中执行了两次jQuery.Callbacks('once memory')
    function createOptions( options ) {
        var object = optionsCache[ options ] = {};
        jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) {
            object[ flag ] = true;
        });
        return object;
    }
    jQuery.Callbacks = function( options ) {  

        // Convert options from String-formatted to Object-formatted if needed
        // (we check in cache first)
        options = typeof options === "string" ?
            ( optionsCache[ options ] || createOptions( options ) ) :
            jQuery.extend( {}, options );  

        var // Flag to know if list is currently firing
            firing,
            // Last fire value (for non-forgettable lists)
            memory,
            // Flag to know if list was already fired
            fired,
            // End of the loop when firing
            firingLength,
            // Index of currently firing callback (modified by remove if needed)
            firingIndex,
            // First callback to fire (used internally by add and fireWith)
            firingStart,
            // Actual callback list
            list = [],
            // Stack of fire calls for repeatable lists
            stack = !options.once && [],
            // Fire callbacks
            fire = function( data ) {
                memory = options.memory && data;
                fired = true;
                firingIndex = firingStart || 0;
                firingStart = 0;
                firingLength = list.length;
                firing = true;
                // 迭代list回调列表,列表中的回调被应用(或执行回调)
                for ( ; list && firingIndex < firingLength; firingIndex++ ) {
                    // 如果回调返回false,且options.stopOnFlase === true,则中断循环
                    // 注:data[1]是一个伪数组(self.fire方法中的arguments(参数集合))
                    if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
                        memory = false; // To prevent further calls using add   // 阻止在add操作中可能执行的回调
                        break;
                    }
                }
                firing = false;
                if ( list ) {
                    // (options.once === undefined),回调对象可以触发多次
                    if ( stack ) {
                        // 处理正在执行的回调中的fireWith操作
                        // 注:如果执行的回调中真的拥有fire或fireWith操作,那么列表中的回调将会无限循环的执行,请看实例1
                        if ( stack.length ) {
                            fire( stack.shift() );
                        }
                    }
                    // (options.once === true && options.memory === true)
                    // 回调列表致空,但允许add继续添加并执行回调
                    else if ( memory ) {
                        list = [];
                    }
                    // (options.once === true && options.memory === undefined)
                    // 禁用回调对象
                    else {
                        self.disable();
                    }
                }
            },
            // Actual Callbacks object
            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, function( _, arg ) {
                                var type = jQuery.type( arg );
                                if ( type === "function" ) {
                                    // 回调不唯一 或 唯一且不存在,则push
                                    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?
                        // 如果正在执行的回调执行了add操作,更新firingLength,将列表中新加进来的最后一个回调加入到回调执行的队列中
                        if ( firing ) {
                            firingLength = list.length;  

                        // With memory, if we're not firing then
                        // we should call right away
                        // 如果可能(options.memory === true),在回调对象不能再次fire(options.once === true)时,
                        // 我们应该使用memory(记录的最后一次fire时的参数)立即调用回调
                        } else if ( 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( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
                                list.splice( index, 1 );
                                // Handle firing indexes
                                // 在回调对象触发(fire)时,如果firingLength、firingIndex(正在执行的回调在列表list中的索引index)
                                // 大于等于 移除的回调的索引(index),分别减一,确保回调执行队列中未执行的回调依次执行
                                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
                // 将列表致空,list = []; firingLenght = 0;
                empty: function() {
                    list = [];
                    firingLength = 0;
                    return this;
                },
                // Have the list do nothing anymore
                // 禁用回调对象
                // 将list赋值为undefined就可以使self中的add,remove,fire,fireWith方法停止工作
                // 我认为这里把stack、memory赋值为undefined与否是没有任何关系的
                disable: function() {
                    list = stack = memory = undefined;
                    return this;
                },
                // Is it disabled?
                disabled: function() {
                    return !list;
                },
                // Lock the list in its current state
                // 锁定回调列表
                // 如果(fired !== true || options.memory === false),则视为禁用(disable)
                // 如果(fired === true && options.memory === true),则视为options.once === true
                // 请看实例2
                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 ) {
                    // 回调对象未执行过fire 或且 可以执行多次(options.once === false)
                    // 如果(fired === true && options.once === true),则不会执行fire操作
                    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;
    }; 
三、实例

实例1、 处理回调函数中的fire,或fireWidth操作

    var cb = jQuery.Callbacks();  

    var fn1 = function(arg){ console.log( arg + '1' ); };
    var fn2 = function(arg){ console.log( arg + '2' ); cb.fire();  };
    var fn3 = function(arg){ console.log( arg + '3' ); };  

    cb.add(fn1, fn2, fn3);  

    cb.fire('fn'); // 其中回调fn1,fn2,fn3无限制的循环调用  

    /*
    控制台将无限制输出如下:
    fn1
    fn2
    fn3
    fn1
    fn2
    fn3
    fn1
    fn2
    fn3
    .
    .
    .
    */ 

实例2、 锁定(lock)操作各种场景中的用法

    var cb1 = jQuery.Callbacks();
    var cb2 = jQuery.Callbacks('memory');
    var cb3 = jQuery.Callbacks('memory');  

    var fn1 = function(arg){ console.log( arg + '1' ); };
    var fn2 = function(arg){ console.log( arg + '2' ); };
    var fn3 = function(arg){ console.log( arg + '3' ); };  

    // 如果options.memory !== true,锁定操作视为禁用回调对象
    cb1.add(fn1);
    cb1.lock();
    // 以下操作无任何反应
    cb1.add(fn2);
    cb1.fire('fn');  

    // 如果fried !== true,锁定操作也视为禁用回调对象
    cb2.add(fn1);
    cb2.lock();
    // 以下操作无任何反应
    cb2.add(fn2);
    cb2.fire('fn');  

    // 如果(fired === true && options.memory === true),锁定操作类似控制标志once(options.once === true);
    cb3.add(fn1);
    cb3.fire('fn'); // fn1,此时fired === true
    cb3.lock();     // 像是传入了'once'标志,jQuery.Callbacks('once memory');
    cb3.add(fn2);   // fn2
    cb3.fire('fn'); // 再次触发,无任何反应
    cb3.add(fn3);   // fn3  

    // 再来看看jQuery.Deferred中的一段源码
    var tuples = [
        // action, add listener, listener list, final state
        [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
        [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
        [ "notify", "progress", jQuery.Callbacks("memory") ]
    ];  

    // Handle state
    if ( stateString ) {
        list.add(function() {
            // state = [ resolved | rejected ]
            state = stateString;  

        // [ reject_list | resolve_list ].disable; progress_list.lock
        }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
    }  

    /*
        当执行了tuples中前面两组中任意一个回调对象的fire方法时,后一组回调对象被锁定,
        相当于(fired === true && options.memory === true),后一组回调对象实际为执行
        jQuery.Callbacks('once memory')生成的回调对象。
    */ 

jQuery回调、递延对象总结(一)jQuery.Callbacks详解的更多相关文章

  1. JQuery在循环中绑定事件的问题详解

    JQuery在循环中绑定事件的问题详解 有个页面上需要N个DOM,每个DOM里面的元素ID都要以数字结尾,比如说 ? 1 2 3 <input type="text" nam ...

  2. JavaScript 对象、DOM对象、jquery对象的区别、转换详解

    一.JavaScript 对象 JavaScript 提供多个内建对象,比如 String.Date.Array 等等. 对象只是带有属性和方法的特殊数据类型. 访问对象的属性: [javascrip ...

  3. JQuery中$.each 和$(selector).each()的区别详解

    PS:晚上在写页面时,发现了一个问题,$.each 和$(selector).each()有哪些区别?百度搜索关键词,首页显示出来一些前人的经验,总结一下,发上来. 1.$(selector).eac ...

  4. jQuery Ajax(load,post,get,ajax)用法与详解

    今天看到群里面有网友们问到Jquery Ajax的(load,post,get,ajax)之间的区别,现在整理了一篇文章出来,希望可以帮到网友们,首先我们先来看一些简单的方法, 这些方法都是对jQue ...

  5. 18.C++-[ ]操作符使用 、函数对象与普通函数区别(详解)

    在上章17.C++-string字符串类(详解)学习了string类,发现可以通过[ ]重载操作符来访问每个字符. 比如: string s="SAD"; for(int i=0, ...

  6. jquery的回调对象Callbacks详解

    Callbacks : 对函数的统一管理 Callbacks的options参数接受4个属性,分别是once : 只执行一次momery : 记忆stopOnFalse : 强制退出循环unique ...

  7. Jquery ajax提交表单几种方法详解

    [导读] 在jquery中ajax提交表单有post与get方式,在使用get方式时我们可以直接使用ajax 序列化表单$( 表单ID) serialize();就行了,下面我来介绍两个提交表单数据的 ...

  8. jquery中attr()与prop()函数用法实例详解(附用法区别)

    本文实例讲述了jQuery中attr()与prop()函数用法.分享给大家供大家参考,具体如下: 一.jQuery的attr()方法 jquery中用attr()方法来获取和设置元素属性,attr是a ...

  9. jquery .post .get中文参数乱码解决方法详解

    jquery默认的编码为utf-8,做项目时有时处于项目需要用到ajax提交中文参数,乱码问题就很头疼了,折腾了许久终于弄出来了.为了便于传输,我们首先将需要用到的参数用javascript自带的函数 ...

随机推荐

  1. 【热门技术】EventBus 3.0,让事件订阅更简单,从此告别组件消息传递烦恼~

    一.写在前面 还在为时间接收而烦恼吗?还在为各种组件间的消息传递烦恼吗?EventBus 3.0,专注于android的发布.订阅事件总线,让各组件间的消息传递更简单!完美替代Intent,Handl ...

  2. 游戏的套路你知道吗? H5 Canvas刮刮乐

    玩游戏的人 很多时候都会遇到翻牌子  开宝箱. 总有人傻傻的在哪里还纠结很久到底点哪一个! 纠结  指不定翻哪一个会多一点,你明明看到那个卡片的奖项多 . 那我就告诉你好了  其实很多时候在你点开那个 ...

  3. MD5 (摘要加密)

    MD5 约定 同样的密码,同样的加密算法,每次加密的结果是不一样 密码方案 方案一:直接 MD5 pwd = pwd.md5String; 非常不安全 方案二 MD5 + 盐 pwd = [pwd s ...

  4. ASP.NET中Web DataGrid的使用指南

    DataGrid/DataList在ASP.NET非常重要,凡显示Table类型的数据,大多会使用这两个控件. 一.方法 1.DataBind很简单.最常用的方法.绑定数据用.需要注意的只有一点:执行 ...

  5. $_POST 变量以及$GLOBALS[&#39;HTTP_RAW_POST_DATA&#39;]

    $_POST 变量是一个数组,内容是由 HTTP POST 方法发送的变量名称和值. $_POST 变量用于收集来自 method="post" 的表单中的值.从带有 POST 方 ...

  6. Esfog_UnityShader教程_逐帧动画

    有段日子没出这个系列的新文章了,今天就拿一个比较常见也比较基础的利用改变Shader来改变不断调整UV实现播放逐帧动画的小功能.很久没写了就当练练手了.在新版本的Unity中早就已经集成了Sprite ...

  7. 【Linux】依赖包检查

    参考:http://www.cnblogs.com/zc22/p/3197038.html ldd xx.so

  8. (翻译)为你的MVC应用程序创建自定义视图引擎

    Creating your own MVC View Engine For MVC Application 原文链接:http://www.codeproject.com/Articles/29429 ...

  9. AppScan8.0简单扫描

    上篇文章介绍了如何在WindowsXP中安装AppScan8.0,接着本篇就来说说怎么进行一次简单的扫描吧. AppScan8.0开始扫描 1.新建扫描,选择“常规扫描”,如下图: (常规.快速.综合 ...

  10. powershell: 生成随机字符串

    ASCII范围内的 获取6个随机字符(字母和数字) 48到57是数字0-9,powershell的范围操作符是..,和Perl 5的一样, 所以 48..57就是指(48 49 50 51 52 53 ...