jquery源码分析(四)——回调对象 Callbacks

借用百度百科来说明下回调函数:

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

jQuery回调对象实现恰好利用了设计模式中的观察者模式思想,观察者模式 (pub/sub) 的背后,总的想法是在应用程序中增强松耦合性。并非是在其它对象的方法上的单个对象调用。一个对象作为特定任务或是另一对象的活动的观察者,并且在这个任务或活动发生时,通知观察者。观察者也被叫作订阅者(Subscriber),它指向被观察的对象,既被观察者(Publisher 或 subject)。当事件发生时,被观察者(Publisher)就会通知观察者(subscriber)

具体到开发中,我们会在事件触发,定时器,ajax、动画(transformend)使用回调函数。

jQuery中回调函数队列管理模块:Callbacks从1.6版中的_Deferred对象中抽离出来的。其设计原理是开始构建一个存放回调的数组,再通过add、remove、fire、lock等操作来控制函数队列的管理,并提供once、memory、unique、stopOnFalse四个option进行一些特殊的控制。

代码如下:

var rnotwhite = (/\S+/g);//匹配空格,返回数组

// String to Object options format cache
var optionsCache = {};

// Convert String-formatted options into Object-formatted ones and store in cache
function createOptions( options ) {
    var object = optionsCache[ options ] = {};
    jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) {//接受的字符串的组合传参数,可以使用空格分割
        object[ flag ] = true;
    });
    return object;
}

/*
 * Create a callback list using the following parameters:

 * By default a callback list will act like an event callback list and can be
 * "fired" multiple times.
 *
 * Possible options://这里提供四个可选参数once、memory、unique、stopOnFalse 对函数队列进行特殊控制。
 */
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 && [],//传递once参数后,则回调执行完毕之后,list会被清空([])或置为undefined
        // Fire callbacks
        fire = function( data ) {//闭包方法
            memory = options.memory && data;
            fired = true;//用来判断回调队列是否被执行过一次
            firingIndex = firingStart || 0;//上次fire的位置,用作设置memory时,add之后直接触发回调。
            firingStart = 0;
            firingLength = list.length;
            firing = true;
            for ( ; list && firingIndex < firingLength; firingIndex++ ) {
                if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {//判断是否需要判读回调返回值
                    memory = false; // To prevent further calls using add---//强制修改memory
                    break;
                }
            }
            firing = false;
            if ( list ) {
                if ( stack ) {//是否为false,初始化时是通过是否传递once来控制,
                    if ( stack.length ) {
                        fire( stack.shift() );
                    }
                } else if ( memory ) {//这里清空目的在于,add之后直接fire新添加的回调。
                    list = [];
                } 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 ) {//添加回调list集合
                        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?
                    if ( firing ) {
                        firingLength = list.length;
                    // With memory, if we‘re not firing then
                    // we should call right away
                    } else if ( memory ) {//这里如果option.memory有值的话,memory已经被修改为[context,arguments]
                        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
                            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 );//若$.Callback调用时设置memory,则会闭包中memory被修改为此时的args<==>[context,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、功能上看:callbacks可以用来在队列中添加回调,执行回调,删除回调等等。并提供一些参数如once,memory,unique等来进行特殊需求的控制。

2、回调模块结构组织:首先构建一个存放回调的队列,如var list = [],通过闭包使回调队列所占内存空间不被释放添加回调时,将回调push进list,fire时遍历list将回调队列中函数执行一遍。

jQuery.Callbacks()的API列表如下

callbacks.add()        往回调队列中添加一个回调或回调的集合。
callbacks.disable()    禁用回调列表中的回调
callbacks.disabled()   判断回调列表是否已被禁用。
callbacks.empty()      从列表中删除所有的回调.
callbacks.fire()       用给定的参数执行所有的回调
callbacks.fired()      判断是否回调队列是否被至少fire一次。
callbacks.fireWith()   访问给定的上下文和参数列表中的执行所有回调。
callbacks.has()        确定列表中是否包含一个回调
callbacks.lock()       锁定当前状态的回调列表。
callbacks.locked()     确定回调列表是否已被锁定。
callbacks.remove()     从回调列表中的删除一个回调或回调集合。

代码定义了多个局部变量,来看看他们用处:

list:保存回掉函数队列  通过闭包使回调数组所占内存空间不被释放,添加回调时,将回调push进list,执行时则遍历list执行回调

firing:判断是否正在执行回调队列。

firingStart:记住当前fire的位置,当参数为memory时使用到。

stack:如果当前正在执行回调(或者说回掉队列未执行完),即firing为true,将要fire的回调保存到栈中,若未设置了once为true ,待回调队列执行完成之后就会执行该回掉。若设置了once 则确保这个回调列表只执行( .fire() )一次。

memory :若设置了memory,保持以前的值,后面添加到这个回调队列会立即执行该回调(像一个递延 Deferred)

firingStart:用于add 回调之后作为立即执行该回调的索引(在设置memory之后使用)

重点提下fire方法: self.fire –> self.fireWith –> fire 最终执行代码是内部私有的fire方法,内部几个逻辑处理了以下几种情况

(1)、每fire 一次都遍历list回调队列,直到结束或者有一个回调函数返回false (只有设置了stopOnFalse控制逻辑才起作用),才中止执行回调

(2)、接下来处理了,回调队列正在执行时(firing为true),add 了一个回调,并不会立即处理而是push 到stack之后。 待回调队列执行完成后再根据stack判断是否fire新增的回调。

disable方法:list = stack = memory = undefined; 后续所有操作都失效

3、参数的使用方式:参数用 一个用空格标记分隔的标志可选列表,用来改变回调列表中的行为:通过以下这些参数进行特殊需求的控制

  • once: 确保这个回调列表只执行( .fire() )一次.
  • memory: 保持以前的值,当执行add之后,将运行添加到这个列表的后面的最新的回调,参数保存在memory中(像一个递延 Deferred).
  • unique: 确保一次只能添加一个回调(所以在列表中没有重复的回调).
  • stopOnFalse: 当一个回调返回false 时中断调用

当新建一个Callbacks回调队列时,可以通过这些参数定义回调队列fire 之后的行为。

默认情况下,回调列表(不传递参数情况下)可以被多次触发(fire),下面来看看参数具体如何使用及其在源码中如何发挥作用的。

(1)、var callbacks = $.Callbacks( "once" ); —— fire一次之后list被清空

(2)、var callbacks = $.Callbacks( "memory" );—— memory的实现思路就是回调队列list在fire之后(不管是否设置once),以后add的时候自动调用fire,将新添加的回调执行一遍。(类似递延)

(3)、var callbacks = $.Callbacks( "unique" );——控制回调函数的唯一性。通过has方法判断是否存在队列中。

(4)、var callbacks = $.Callbacks( "stopOnFalse" );——控制是否需要回调函数返回值是否为false来终止回调队列的执行。

(5)、var callbacks = $.Callbacks( "once memory" );—— 保持以前的值,将添加到这个列表的后面的最新的值立即执行调用任何回调,once的时候只允许add一次,在触发fire之后就会理清掉list。

时间: 2024-12-26 15:24:35

jquery源码分析(四)——回调对象 Callbacks的相关文章

jquery源码分析(二)——结构

再来复习下整体架构: jQuery源码分析(基于 jQuery 1.11 版本,共计8829行源码) (21,94)                定义了一些变量和函数jQuery=function(){} (96,280)        给jQuery添加一些方法和属性,jQuery.fn=jQuery.prototype(285,347)        extend:        jQuery的一些继承方法        更容易进行后续的扩展                       

jQuery源码分析系列(38) : 队列操作

Queue队列,如同data数据缓存与Deferred异步模型一样,都是jQuery库的内部实现的基础设施 Queue队列是animate动画依赖的基础设施,整个jQuery中队列仅供给动画使用 Queue队列 队列是一种特殊的线性表,只允许在表的前端(队头)进行删除操作(出队),在表的后端(队尾)进行插入操作(入队).队列的特点是先进先出(FIFO-first in first out),即最先插入的元素最先被删除. 为什么要引入队列? 我们知道代码的执行流有异步与同步之分,例如 var a

jQuery源码分析系列(35) : Ajax - jsonp的实现与原理

ajax的核心是通过XmlHttpRequest获取非本页内容,而jsonp的核心则是动态添加<script>标签来调用服务器提供的js脚本 json核心就是:允许用户传递一个callback参数给服务端,然后服务端返回数据时会将这个callback参数作为函数名来包裹住JSON数据,这样客户端就可以随意定制自己的函数来自动处理返回数据了. jquery ext dojo这类库的实现手段其实大同小异 在同源策略下,在某个服务器下的页面是无法获取到该服务器以外的数据的,但img.iframe.s

jQuery源码分析系列(37) : Ajax 总结

综合前面的分析,我们总结如下3大块: jQuery1.5以后,AJAX模块提供了三个新的方法用于管理.扩展AJAX请求 前置过滤器 jQuery. ajaxPrefilter 请求分发器 jQuery. ajaxTransport 类型转换器 ajaxConvert 为了整体性与扩展性考虑,把整个结构通过Deferred实现异步链式模型,Promise对象可以轻易的绑定成功.失败.进行中三种状态的回调函数,然后通过在状态码在来回调不同的函数就行了 出于同源策略考虑,存在跨域问题,所以ajax内部

jQuery源码分析系列(31) : Ajax deferred实现 - Aaron.

AJAX的底层实现都是浏览器提供的,所以任何基于api上面的框架或者库,都只是说对于功能的灵活与兼容维护性做出最优的扩展 ajax请求的流程: 1.通过 new XMLHttpRequest 或其它的形式(指IE)生成ajax的对象xhr. 2.通过xhr.open(type, url, async, username, password)的形式建立一个连接. 3.通过setRequestHeader设定xhr的请求头部(request header). 4.通过send(data)请求服务器端

[转]jQuery源码分析系列

文章转自:jQuery源码分析系列-Aaron 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://github.com/JsAaron/jQuery 正在编写的书 - jQuery架构设计与实现 本人在慕课网的教程(完结) jQuery源码解析(架构与依赖模块) 64课时 jQuery源码解析(DOM与核心模块)64课时 jQuery源码分析目录(完结) jQuery源码分析系列(01) : 整体架构 jQuery源码分析系列(

jQuery源码分析系列(33) : AJAX中的前置过滤器和请求分发器

jQuery1.5以后,AJAX模块提供了三个新的方法用于管理.扩展AJAX请求,分别是: 1.前置过滤器 jQuery. ajaxPrefilter 2.请求分发器 jQuery. ajaxTransport, 3.类型转换器 ajaxConvert 源码结构: jQuery.extend({ /** * 前置过滤器 * @type {[type]} */ ajaxPrefilter: addToPrefiltersOrTransports(prefilters), /** * 请求分发器 *

Jquery源码分析

1.概述 jQuery是一个非常优秀的Js库,与prototype,YUI,Mootools等众多的Js类库相比,它剑走偏锋,从web开发最实用的角度出发,抛除了一些中看但不实用的东西,为开发者提供一个短小精悍的类库.由于其个短小精悍,使用简单方便,性能相对高效.众多的开发者都选择Jquery来进行辅助的web开发. 在使用jquery时开发,我们也会时常碰到许多的问题,但是jquery的代码很晦涩,难起看懂,当开发时出现了问题,看不懂源码,不知道如何去排错. John Resig,Jquery

jQuery源码分析系列(36) : Ajax - 类型转化器

什么是类型转化器? jQuery支持不同格式的数据返回形式,比如dataType为 xml, json,jsonp,script, or html 但是浏览器的XMLHttpRequest对象对数据的响应只有 responseText与responseXML 二种 所以现在我要定义dataType为jsonp,那么所得的最终数据是一个json的键值对,所以jQuery内部就会默认帮你完成这个转化工作 jQuery为了处理这种执行后数据的转化,就引入了类型转化器,如果没有指定类型就依据响应头Con