jQuery.Callbacks()

jQuery.Callbacks( flags )返回类型:Callbacks

描述:一个多目的的回调函数列表对象,提供了一个强大的方式,来管理回调函数列表。

$.Callbacks()函数内部用来提供jQuery$.ajax()$.Deferred()组件背后的基本功能。它还可以用作类似的基础,用来为新组件定义功能。

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

起步

下面是两个示例方法,命名为fn1fn2

1
2
3
4
5
6
7
8
function fn1( value ) {
console.log( value );
}
function fn2( value ) {
console.log( "fn2 says: " + value );
return false;
}

可以把这些方法添加为$.Callbacks列表的回调函数,并按如下调用它们:

1
2
3
4
5
6
7
8
9
10
var callbacks = $.Callbacks();
callbacks.add( fn1 );
// Outputs: foo!
callbacks.fire( "foo!" );
callbacks.add( fn2 );
// Outputs: bar!, fn2 says: bar!
callbacks.fire( "bar!" );

结果是构造回调函数的复杂列表变得很简单,可以轻松传入需要的函数,通过函数来传入输入值。

上面用了两个特定的方法:.add().fire().add()方法支持向回调函数列表添加新的回调函数,而.fire()方法执行了添加的函数,提供了一个传输参数的方法,在同一个列表中由回调函数处理它们。

另一个由$.Callbacks支持的方法是.remove(),它有能力从回调函数列表中删除特定的回调函数。下面是使用.remove()的实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var callbacks = $.Callbacks();
callbacks.add( fn1 );
// Outputs: foo!
callbacks.fire( "foo!" );
callbacks.add( fn2 );
// Outputs: bar!, fn2 says: bar!
callbacks.fire( "bar!" );
callbacks.remove( fn2 );
// Only outputs foobar, as fn2 has been removed.
callbacks.fire( "foobar" );

受支持的标记

flags参数是对$.Callbacks()可选的参数,结构化为空格分隔的字符串列表,改变了回调函数列表的行为(例如,$.Callbacks( "unique stopOnFalse" ))。

可用的标记

  • once: 确保回调函数列表只可被引发一次(譬如延迟对象)。
  • memory: 保持先前的值的跟踪,在列表已经引发之后添加的回调函数会被立即用最新的“memorized”引发值引发(譬如延迟对象)。
  • unique: 确保一次只能添加一个回调函数(所以列表中没有重复的回调函数)。
  • stopOnFalse: 当一个回调返回false时中断调用。

默认情况下,回调函数列表将表现得像事件回调函数列表,可以被“引发”多次。

如何在理想情况下应该使用的flags,请看下面:

$.Callbacks( "once" ):

1
2
3
4
5
6
7
8
9
10
11
12
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" ):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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" ):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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" ):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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()支持一些标记,而不只支持一个标记,所以设置一些标记具有累积效应,类似于“&&”。这意味着它可能结合标记来创建回调函数列表,也就是说,唯一性并且确保列表已经引发,添加更多回调函数将使它用最后引发的值调用。(亦即,$.Callbacks("unique memory"))。

$.Callbacks( 'unique memory' ):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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
*/

针对延迟对象上的.done()函数和.fail()函数,标记与$.Callbacks()在jQuery中内部组合,两者都使用$.Callbacks('memory once')

也可以分离$.Callbacks的方法,为了方便起见,应该定义一个简写版本:

1
2
3
4
5
6
7
8
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(发布/订阅,或者观察者模式)背后的一般思路是促进应用程序的松散耦合。Rather than single objects calling on the methods of other objects, an object instead subscribes to a specific task or activity of another object and is notified when it occurs. 观察者也被称为订阅者,我们将被观察的对象称为发布者(或主题)。当事件发生时,发布者通知订阅者。

To demonstrate the component-creation capabilities of $.Callbacks(), it's possible to implement a Pub/Sub system using only callback lists. Using $.Callbacks as a topics queue, a system for publishing and subscribing to topics can be implemented as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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;
};

This can then be used by parts of your application to publish and subscribe to events of interest quite easily:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 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!
*/

While this is useful, the implementation can be taken further. Using $.Deferreds, it's possible to ensure publishers only publish notifications for subscribers once particular tasks have been completed (resolved). See the below code sample for some further comments on how this could be used in practice:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 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( "it's been published!" );