观察者模式是开发中经常使用的模式,这个模式由两个主要部分组成:主题和观察者。通过观察者模式,实现主题和观察者的解耦。

主题负责发布内容,而观察者则接收主题发布的内容。通常情况下,观察者都是多个,所以,我们需要一个集合来保存所有的观察者,在主题发布内容之后,依次将主题发布的内容提供给观察者,从程序的角度来说,观察者就是一堆的方法,我们将内容作为参数依次调用这些方法。

如果你已经看过上一篇 jQuery 之 Callbacks, 那么,你会发现,通过 Callbacks 来管理观察者的列表是很方便的事情。再添加一个主题,我们就可以实现观察者模式了。在 jQuery 中,这个模式被到处使用,不要说你没有使用过 ready 函数,回想一下,你可以在一个页面中,多次使用 ready 函数,在 ready 事件触发之后,这些函数就可以被依次调用了,这个机制就已经突破了简单的事件处理机制了。

需要说明的是,许多事件仅仅触发一次,比如 ready 事件,ajax 的请求处理等等,这种情况下,使用 Deferred 就非常方便了。

使用 Deferred

在 jQuery 中,实现观察者模式的就是 Deferred 了,我们先看它的使用。你也可以直接看 jQuery 的 Deferred 文档

这个对象提供了主题和订阅的管理,使用它可以很容易实现一次性的观察者模式。

// 定义主题
var subject = (function(){
var dfd = $.Deferred(); return dfd;
})(); // 两个观察者
var fn1 = function(content){
console.log("fn1: " + content );
} var fn2 = function(content){
console.log("fn2: " + content );
} // 注册观察者
$.when( subject )
.done( fn1 )
.done( fn2 ); // 发布内容
subject.resolve("Alice");

通常我们在主题内部来决定什么时候,以及发布什么内容,而不允许在主题之外发布。通过 Deferred 对象的 promise 方法,我们可以只允许在主题之外注册观察者,有点像 .NET 中 event 的处理了。这样,我们的代码就成为下面的形式。

// 定义主题
var subject = (function(){
var dfd = $.Deferred(); var task = function()
{
// 发布内容
dfd.resolve("Alice");
} setTimeout( task, 3000); return dfd.promise();
})(); // 两个观察者
var fn1 = function(content){
console.log("fn1: " + content );
} var fn2 = function(content){
console.log("fn2: " + content );
} // 注册观察者
$.when( subject )
.done( fn1 )
.done( fn2 );

在 jQuery 中,甚至可以提供两个主题同时被观察, 需要注意的是,要等两个主题都触发之后,才会真正触发,每个观察者一次得到这两个主题,所以参数变成了 2 个。

// 定义主题
var subjectAlice = (function(){
var dfd = $.Deferred(); var task = function()
{
// 发布内容
dfd.resolve("Alice");
} setTimeout( task, 3000); return dfd.promise();
})(); var subjectTom = (function(){
var dfd = $.Deferred(); var task = function()
{
// 发布内容
dfd.resolve("Tom");
} setTimeout( task, 1000); return dfd.promise();
})(); // 两个观察者
var fn1 = function(content1, content2){
console.log("fn1: " + content1 );
console.log("fn1: " + content2 );
} var fn2 = function(content1, content2){
console.log("fn2: " + content1 );
console.log("fn2: " + content2 );
} // 注册观察者
$.when( subjectAlice, subjectTom )
.done( fn1 )
.done( fn2 );

实际上,在 jQuery 中,不仅可以发布成功完成的事件,主题还可以发布其它两种事件:失败和处理中。

失败事件,通过调用主题的 reject 方法可以发布失败的消息,对于观察者来说,需要通过 fail 来注册这个事件了。

处理中事件,通过调用主题的 notify 来发布处理中的消息,对于观察者来说,需要通过 progress 来注册这个事件。

要是观察者想一次性注册多个事件,那么,可以通过 then 来注册,这种方式可以处理主题的成功、失败和处理中三种事件。

$.get( "test.php" ).then(
function() {
alert( "$.get succeeded" );
}, function() {
alert( "$.get failed!" );
}
);

只考虑成功和失败的话,就通过 always 来处理。

$.get( "test.php" )
.always(function() {
alert( "$.get completed with success or error callback arguments" );
});

jQuery 中 Deferred 的使用

常用的是 ajax, get, post 等等 Ajax 函数了。它们内部都已经实现为了 Deferred ,返回的结果就是 Deferred 对象了。也就是说你只管写观察者就可以了,主题内部已经处理好了,比如当 ajax 成功之后调用 resolve 来触发 done 事件并传递参数的问题。你可以继续使用传统的回调方式,显然推荐你使用 Deferred 方式了。这样你的代码结构更加清晰。

$.post( "test.php", { name: "John", time: "2pm" })
.done(function( data ) {
alert( "Data Loaded: " + data );
});

如果需要访问两个 Ajax ,则可以这样

$.when( $.post( "test.php", { name: "John", time: "2pm" }),
$.post( "other.php" ) )
.done(function( data1, data2 ) {
alert( "Data Loaded: " + data1 );
alert( "Data Loaded: " + data2 );
});

实现 Deferred

通过前面的使用,其实你一定可以想到,在 Deferred 这个对象的内部,必须有三个回调队列了,这里的成功和失败只能一次完成,所以这两个 Callbacks 都使用了 once 来定义。

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") ]
],

当前处理的状态。

state = "pending",
promise = {
state: function() {
return state;
},

always 就是直接注册了两个事件。then 允许我们一次处理三种注册。

always: function() {
deferred.done( arguments ).fail( arguments );
return this;
},
then: function( /* fnDone, fnFail, fnProgress */ ) {
var fns = arguments;
return jQuery.Deferred(function( newDefer ) {
jQuery.each( tuples, function( i, tuple ) {
var action = tuple[ 0 ],
fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
// deferred[ done | fail | progress ] for forwarding actions to newDefer
deferred[ tuple[1] ](function() {
var returned = fn && fn.apply( this, arguments );
if ( returned && jQuery.isFunction( returned.promise ) ) {
returned.promise()
.done( newDefer.resolve )
.fail( newDefer.reject )
.progress( newDefer.notify );
} else {
newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
}
});
});
fns = null;
}).promise();
},

deferred 就是一个对象。pipe 是已经过时的用法,是 then 的别名。

deferred = {};

    // Keep pipe for back-compat
promise.pipe = promise.then; // Add list-specific methods
jQuery.each( tuples, function( i, tuple ) {
var list = tuple[ 2 ],
stateString = tuple[ 3 ]; // promise[ done | fail | progress ] = list.add
promise[ tuple[1] ] = list.add; // 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 );
} // deferred[ resolve | reject | notify ]
deferred[ tuple[0] ] = function() {
deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );
return this;
};
deferred[ tuple[0] + "With" ] = list.fireWith;
}); // Make the deferred a promise
promise.promise( deferred ); // Call given func if any
if ( func ) {
func.call( deferred, deferred );
} // All done!
return deferred;
},

when 是一个助手方法,支持多个主题。

// Deferred helper
when: function( subordinate /* , ..., subordinateN */ ) {
var i = 0,
resolveValues = core_slice.call( arguments ),
length = resolveValues.length, // the count of uncompleted subordinates
remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0, // the master Deferred. If resolveValues consist of only a single Deferred, just use that.
deferred = remaining === 1 ? subordinate : jQuery.Deferred(), // Update function for both resolve and progress values
updateFunc = function( i, contexts, values ) {
return function( value ) {
contexts[ i ] = this;
values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value;
if( values === progressValues ) {
deferred.notifyWith( contexts, values );
} else if ( !( --remaining ) ) {
deferred.resolveWith( contexts, values );
}
};
}, progressValues, progressContexts, resolveContexts; // add listeners to Deferred subordinates; treat others as resolved
if ( length > 1 ) {
progressValues = new Array( length );
progressContexts = new Array( length );
resolveContexts = new Array( length );
for ( ; i < length; i++ ) {
if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
resolveValues[ i ].promise()
.done( updateFunc( i, resolveContexts, resolveValues ) )
.fail( deferred.reject )
.progress( updateFunc( i, progressContexts, progressValues ) );
} else {
--remaining;
}
}
} // if we're not waiting on anything, resolve the master
if ( !remaining ) {
deferred.resolveWith( resolveContexts, resolveValues );
} return deferred.promise();
}

完整的代码如下所示:

jQuery.extend({

    Deferred: function( func ) {
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") ]
],
state = "pending",
promise = {
state: function() {
return state;
},
always: function() {
deferred.done( arguments ).fail( arguments );
return this;
},
then: function( /* fnDone, fnFail, fnProgress */ ) {
var fns = arguments;
return jQuery.Deferred(function( newDefer ) {
jQuery.each( tuples, function( i, tuple ) {
var action = tuple[ 0 ],
fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
// deferred[ done | fail | progress ] for forwarding actions to newDefer
deferred[ tuple[1] ](function() {
var returned = fn && fn.apply( this, arguments );
if ( returned && jQuery.isFunction( returned.promise ) ) {
returned.promise()
.done( newDefer.resolve )
.fail( newDefer.reject )
.progress( newDefer.notify );
} else {
newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
}
});
});
fns = null;
}).promise();
},
// Get a promise for this deferred
// If obj is provided, the promise aspect is added to the object
promise: function( obj ) {
return obj != null ? jQuery.extend( obj, promise ) : promise;
}
},
deferred = {}; // Keep pipe for back-compat
promise.pipe = promise.then; // Add list-specific methods
jQuery.each( tuples, function( i, tuple ) {
var list = tuple[ 2 ],
stateString = tuple[ 3 ]; // promise[ done | fail | progress ] = list.add
promise[ tuple[1] ] = list.add; // 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 );
} // deferred[ resolve | reject | notify ]
deferred[ tuple[0] ] = function() {
deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );
return this;
};
deferred[ tuple[0] + "With" ] = list.fireWith;
}); // Make the deferred a promise
promise.promise( deferred ); // Call given func if any
if ( func ) {
func.call( deferred, deferred );
} // All done!
return deferred;
}, // Deferred helper
when: function( subordinate /* , ..., subordinateN */ ) {
var i = 0,
resolveValues = core_slice.call( arguments ),
length = resolveValues.length, // the count of uncompleted subordinates
remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0, // the master Deferred. If resolveValues consist of only a single Deferred, just use that.
deferred = remaining === 1 ? subordinate : jQuery.Deferred(), // Update function for both resolve and progress values
updateFunc = function( i, contexts, values ) {
return function( value ) {
contexts[ i ] = this;
values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value;
if( values === progressValues ) {
deferred.notifyWith( contexts, values );
} else if ( !( --remaining ) ) {
deferred.resolveWith( contexts, values );
}
};
}, progressValues, progressContexts, resolveContexts; // add listeners to Deferred subordinates; treat others as resolved
if ( length > 1 ) {
progressValues = new Array( length );
progressContexts = new Array( length );
resolveContexts = new Array( length );
for ( ; i < length; i++ ) {
if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
resolveValues[ i ].promise()
.done( updateFunc( i, resolveContexts, resolveValues ) )
.fail( deferred.reject )
.progress( updateFunc( i, progressContexts, progressValues ) );
} else {
--remaining;
}
}
} // if we're not waiting on anything, resolve the master
if ( !remaining ) {
deferred.resolveWith( resolveContexts, resolveValues );
} return deferred.promise();
}
});

jquery 之 Deferred 使用与实现的更多相关文章

  1. jQuery之Deferred源码剖析

    一.前言 大约在夏季,我们谈过ES6的Promise(详见here),其实在ES6前jQuery早就有了Promise,也就是我们所知道的Deferred对象,宗旨当然也和ES6的Promise一样, ...

  2. jQuery的deferred对象详解

    jQuery的deferred对象详解请猛击下面的链接 http://www.ruanyifeng.com/blog/2011/08/a_detailed_explanation_of_jquery_ ...

  3. jQuery的deferred对象学习

    #copy { background-color: lightgreen; padding: 15px; margin: 10px } 一.deferred对象简介 deferred对象是jquery ...

  4. jQuery的deferred对象详解(转载)

    本文转载自: jQuery的deferred对象详解(转载)

  5. jQuery的Deferred

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...

  6. jQuery的deferred对象

    应用场景:处理异步任务 看到一篇阮一峰老师的博客挺好的讲的就是jQuery的deferred对象.坦诚讲之前没有怎么用过这个东东呢. 摘其中几点记录下 (1) $.Deferred() 生成一个def ...

  7. jQuery的deferred对象详解(一)

    最近一段时间,都在研究jquery里面的$.Deffered对象,几天都搞不明白,其中源码的运行机制,网上查找了相关的资料,<jQuery的deferred对象详解>阮一峰老师的文章,里面 ...

  8. jQuery的deferred对象解析

    参考: jQuery的deferred对象详解:http://www.ruanyifeng.com/blog/2011/08/a_detailed_explanation_of_jquery_defe ...

  9. [转] jQuery的deferred对象详解

    jQuery的开发速度很快,几乎每半年一个大版本,每两个月一个小版本. 每个版本都会引入一些新功能.今天我想介绍的,就是从jQuery 1.5.0版本开始引入的一个新功能----deferred对象. ...

随机推荐

  1. Fix git 提交代码错误

    今天用git clone下代码,修改,push提交,发现以下错误 [root@localhost gocache]# git push origin master error: The request ...

  2. [XAF] 多级联列表显示

    XAF给的例子已经实现,详细可查看例子中的代码. 工作车间--工作中心--机器

  3. selenium 富文本框处理

    selenium 富文本框处理, 网上有用API的解决方法1:参见:http://blog.csdn.net/xc5683/article/details/8963621 群里1位群友的解决方法2:参 ...

  4. [翻译]Spring框架参考文档(V4.3.3)-第二章Spring框架介绍 2.1 2.2 翻译--2.3待继续

    英文链接:http://docs.spring.io/spring-framework/docs/current/spring-framework-reference/html/overview.ht ...

  5. nginx.conf配置(支持thinkphp)

    error_log /home/wwwlogs/nginx_error.log crit; pid /usr/local/nginx/logs/nginx.pid; #Specifies the va ...

  6. gulp插件(gulp-jmbuild),用于WEB前端构建

    源码地址:https://github.com/jiamao/gulp-jmbuild https://github.com/jiamao/gulp-jmbuild gulp-jmbuild gulp ...

  7. array new 与 array deletedelete

    以前在看C++书和上C++课的时候可以看到 delete[] pointer; 的用法,而大多数对于这个用法没有具体的解释,多是看到: 有一个delete运算符的特殊语法,可以释放动态分配的数组内存: ...

  8. Win10 UWP开发中的重复性静态UI绘制小技巧 1

    介绍 在Windows 10 UWP界面实现的过程中,有时会遇到一些重复性的.静态的界面设计.比如:画许多等距的线条,画一圈时钟型的刻度线,同特别的策略排布元素,等等. 读者可能觉得这些需求十分简单, ...

  9. 一天一小段js代码(no.4)

    最近在看网上的前端笔试题,借鉴别人的自己来试一下: 题目: 写一段脚本,实现:当页面上任意一个链接被点击的时候,alert出这个链接在页面上的顺序号,如第一个链接则alert(1), 依次类推. 有一 ...

  10. zendframework 2 链接数据库

    相对于zf1,来说,zf2让我们对于数据库这方面的操作我的个人感觉是对于字段起别名简单了,但是对数据库的操作虽然配置写好的就基本不需要动了,但是还是比1的配置要繁琐, 还是那句话,大家可以去看看源码. ...