jQuery.Callbacks(flags)

一个多用途的回调函数列表对象,提供一种强大的方式来管理回调函数列表。

jQuery $.ajax()$.Deferred() 组件的内部会使用 $.Callbacks() 函数,来提供基本的功能。它可以用作定义新组件功能的简易基础。

$.Callbacks() 支持的方法包括 callbacks.add() , callbacks.remove() , callbacks.fire() callbacks.disable() .

开始学习

例如,有如下两个方法,分别叫做 fn1fn2:

function fn1( value ){
    console.log( value );
}

function fn2( value ){
    fn1("fn2 says:" + value);
    return false;
}

可以将上述两个方法作为回调函数,并添加到 $.Callbacks 列表中,并按下面的顺序调用它们:

var callbacks = $.Callbacks();
callbacks.add( fn1 );
callbacks.fire( "foo!" ); // outputs: foo!

callbacks.add( fn2 );
callbacks.fire( "bar!" ); // outputs: bar!, fn2 says: bar!

这样做的结果是,当构造复杂的回调函数列表时,将会变更很简单。可以根据需要,很方面的就可以向这些回调函数中传入所需的参数。

上面的例子中,我们使用了 $.Callbacks() 的两个方法: .add().fire()。 .add() 可以向回调函数列表中添加新的回调函数,fire() 可以向回调函数中传递参数,并执行回调函数。

$.Callbacks 支持的另一个方法是 remove(),它可以从回调函数列表中移除指定的回调函数。下面是使用 .remove() 的例子:

var callbacks = $.Callbacks();
callbacks.add( fn1 );
callbacks.fire( "foo!" ); // outputs: foo!

callbacks.add( fn2 );
callbacks.fire( "bar!" ); // outputs: bar!, fn2 says: bar!

callbacks.remove(fn2);
callbacks.fire( "foobar" ); 

// only outputs foobar, as fn2 has been removed.

支持的标识

flags 参数是 $.Callbacks() 的可选参数,该参数是一个以空格分隔的字符串列表,代表如何改变回调函数列表的行为(例如,$.Callbacks( 'unique stopOnFalse' ))。

可用的标识:

  • once: 保证回调函数列表只能被 .fire() 一次。(就像延迟对象一样)
  • memory: 持续保留前一个值,在执行完 .fire() 之后添加的任何回调函数,当再次遇到 .fire() 时,会将“最新保持”的值作为参数,立刻调用最近添加的所有函数,然后再执行回调函数列表中的函数(就像延迟对象一样)。
  • unique: 保证一个回调函数只能被添加一次(也就是说,在回调函数列表中,没有重复的回调函数)。
  • stopOnFalse: 当回调函数返回 false 时,中断调用。

默认情况下,一个回调函数列表就像一个事件回调列表,可以被“调用”多次。

下面是一些理想情况下,使用 flags 的范例:

$.Callbacks( 'once' ):

var callbacks = $.Callbacks( "once" );
callbacks.add( fn1 );
callbacks.fire( "foo" );
callbacks.add( fn2 );
callbacks.fire( "bar" );
callbacks.remove( fn2 );
callbacks.fire( "foobar" );

/*
output: 
foo
*/

$.Callbacks( 'memory' ):

var callbacks = $.Callbacks( "memory" );
callbacks.add( fn1 );
callbacks.fire( "foo" );
callbacks.add( fn2 );
callbacks.fire( "bar" );
callbacks.remove( fn2 );
callbacks.fire( "foobar" );

/*
output:
foo
fn2 says:foo
bar
fn2 says:bar
foobar
*/

$.Callbacks( 'unique' ):

var callbacks = $.Callbacks( "unique" );
callbacks.add( fn1 );
callbacks.fire( "foo" );
callbacks.add( fn1 ); // repeat addition
callbacks.add( fn2 );
callbacks.fire( "bar" );
callbacks.remove( fn2 );
callbacks.fire( "foobar" );

/*
output:
foo
bar
fn2 says:bar
foobar
*/

$.Callbacks( 'stopOnFalse' ):

function fn1( value ){
    console.log( value );
    return false;
}

function fn2( value ){
    fn1("fn2 says:" + value);
    return false;
}

var callbacks = $.Callbacks( "stopOnFalse");
callbacks.add( fn1 );
callbacks.fire( "foo" );
callbacks.add( fn2 );
callbacks.fire( "bar" );
callbacks.remove( fn2 );
callbacks.fire( "foobar" );

/*
output:
foo
bar
foobar
*/

由于 $.Callbacks() 可以同时使用多个标识,因此,设置多个标识时,有累积的效果,相当于 "&&"。这意味着,在创建回调函数列表时,可以使用组合标识,例如,同时使用 uniquememory$.Callbacks("unique memory")),既保证回调函数列表中函数的唯一性,也可以保证在执行完函数后再添加的函数被调用时,可以使用上次调用时使用的参数。

$.Callbacks( 'unique memory' ):

function fn1( value ){
    console.log( value );
    return false;
}

function fn2( value ){
    fn1("fn2 says:" + value);
    return false;
}
    
var callbacks = $.Callbacks( "unique memory" );
callbacks.add( fn1 );
callbacks.fire( "foo" );
callbacks.add( fn1 ); // repeat addition
callbacks.add( fn2 );
callbacks.fire( "bar" );
callbacks.add( fn2 );
callbacks.fire( "baz" );
callbacks.remove( fn2 );
callbacks.fire( "foobar" );

/*
output:
foo
fn2 says:foo
bar
fn2 says:bar
baz
fn2 says:baz
foobar
*/

jQuery 在延迟对象的 .done().fail() 中应用 $.Callbacks() 时也使用了标识组合,同时使用了 $.Callbacks('memory once')

$.Callbacks 方法同样可以被分离出来,这样就可以很方便的创建简单的别名:

var callbacks = $.Callbacks(),
    add = callbacks.add,
    remove = callbacks.remove,
    fire = callbacks.fire;

add( fn1 );
fire( "hello world");
remove( fn1 );

$.Callbacks, $.Deferred 和 Pub/Sub(观察者模式)

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

作为 $.Callbacks() 的创建组件的一个演示,只使用回调函数列表,就可以实现 Pub/Sub 系统。将 $.Callbacks 作为一个文章队列,可以向下面这样,实现文章的发布和订阅:

var topics = {};

jQuery.Topic = function( id ) {
    var callbacks,
        method,
        topic = id && topics[ id ];
    if ( !topic ) {
        callbacks = jQuery.Callbacks();
        topic = {
            publish: callbacks.fire,
            subscribe: callbacks.add,
            unsubscribe: callbacks.remove
        };
        if ( id ) {
            topics[ id ] = topic;
        }
    }
    return topic;
};

下面的代码可以作为你应用程序的一部分,可以相当简单的完成对感兴趣的文章的发布和订阅事件:

// Subscribers
$.Topic( "mailArrived" ).subscribe( fn1 );
$.Topic( "mailArrived" ).subscribe( fn2 );
$.Topic( "mailSent" ).subscribe( fn1 );

// Publisher
$.Topic( "mailArrived" ).publish( "hello world!" );
$.Topic( "mailSent" ).publish( "woo! mail!" );

// Here, "hello world!" gets pushed to fn1 and fn2
// when the "mailArrived" notification is published
// with "woo! mail!" also being pushed to fn1 when
// the "mailSent" notification is published. 

/*
output:
hello world!
fn2 says: hello world!
woo! mail!
*/

尽管上面的代码很有用,但是可以进一步改进其实现。使用 $.Deferreds,可以保证当特定的任务被完成(或被解决)时,发布者只能向订阅者发布通知。参见下面的示例代码,进一步讨论如何在实践中使用这种情况:

// subscribe to the mailArrived notification
$.Topic( "mailArrived" ).subscribe( fn1 );

// create a new instance of Deferreds
var dfd = $.Deferred();

// define a new topic (without directly publishing)
var topic = $.Topic( "mailArrived" );

// when the deferred has been resolved, publish a 
// notification to subscribers
dfd.done( topic.publish );

// Here the Deferred is being resolved with a message
// that will be passed back to subscribers. It's possible to
// easily integrate this into a more complex routine
// (eg. waiting on an ajax call to complete) so that
// messages are only published once the task has actually
// finished.
dfd.resolve( "its been published!" );