作者:禅楼望月(http://www.cnblogs.com/yaoyinglong

1 引子

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

发布者主要负责发布内容,观察者主要负责监听发布者发布的内容,并作出相应的动作。和我们平时订阅期刊一样,cnki会维护一个订阅者列表,有期刊被发布出来时,cnki会将这些期刊推送给订阅者。从程序角度来说,订阅者就是一堆的方法,发布者的推送内容的动作就是依次调用订阅者列表中的方法(订阅者),而发布的内容就将以参数的形式提供给订阅者。

在jQuery中Deferred就是用来实现这一模式的。

我们首先来一个简单的订阅与发布示例:

[+]view code
 

//创建一个发布者

var subject=$.Deferred();

//创建两个订阅者

function subscriber1(content){

    console.log(content);

}

function subscriber2(content){

    console.log(content);

}

//订阅内容

subject.done(subscriber1,subscriber2);

//发布内容

subject.resolve('谢谢你的订阅');

下面我们来分析,$.Deferred的源码,在这个过程中,我们还会$.Deferred的各种使用情况。

2 构建Deferred

首先我们将Deferred源码做简化处理:

其中,tuples是一种数据结构。它是一种非常巧妙的设计,是把有共性的代码合并到一起,然后一次性进行处理。promise是deferred的一种简化形式(去掉了改变状态的接口)。Deferred是一个工厂类,返回的是内部创建好的deferred对象。

[+]view code
 

var tuples =[
  // action, add listener, listener list, final state
  //once 表示resolve和reject只要触发过一次,后面就不能再被触发了
  ["resolve","done", jQuery.Callbacks("once memory"),"resolved"],
  ["reject","fail", jQuery.Callbacks("once memory"),"rejected"],
  ["notify","progress", jQuery.Callbacks("memory")]
]

tuples定义3中状态的,即resolve,reject,notify。这三种状态分别对应了3中订阅接口(done,fail,progress)和3种订阅列表(3个Callbacks),resolve和reject有最终的状态(resolved和rejected)。下面就是为找这个数据结构,来构建带这3中状态的Deferred。

首先定义了初始状态:state = "pending"

然后构建promise对象:

在构建deferred对象:

构建deferred分为2步:

1. 构建3种状态对应的接口,订阅者列表,和推送内容接口。

构建订阅者列表,行,列表由一个Callbacks来管理,其实整个Deferred就是基于Callbacks来实现的,把它们的代码放在一起对比,会发现出奇的相似,构建状态字符串(分别为resolved、rejected或者undefined)。

$.each执行完会有3个列表生成:

doneList=$.Callbacks("once memory");
failList=$.Callbacks("once memory");
progressList=jQuery.Callbacks("memory");

定义订阅接口行,他们就是订阅列表(Callbacks)的add方法。add进去的订阅者会存储在Callbacks的list数组中。

3中订阅接口分别为:

promise.done=doneList.add;
promise.fail=failList.add;
promise.progress=progressList.add

为resolved和rejected两种状态的列表添加预设的3个回调函数(3437~3444)。第一个回调函数用于处理Deferred的状态(已成功或已失败),第二个回调函数用于让另一个列表失效,这是因为当发布者发布内容的动作已经成功了,它就不可能再触发失败的状态,因此让失败列表失效,防止给正在订阅了失败状态的函数被执行。反之亦然。然后锁定notify列表。锁定notify只是让其的progressList的fire方法(即后面不能使用notify和notifyWith再进行推送内容了)不能被调用了。但是,我们还可以给notify状态继续添加订阅者,这些订阅者会立即被执行。

这里有个小技巧:i^1,^是按位运算符

没什么大用,但是jQuery就是要连这点性能也要节省出来。

至此,doneList的list中有3个回调函数[changeState, failList.disable, progressList.lock],failList的list中同样有3个回调函数[changeState, doneList.disable, progressList.lock]

构建推送内容接口(3447~3451),三种接口名字分别为

deferred.resolve(成功),deferred.reject(失败),deferred.notify(正在执行),这三个接口在内部其实又去调用这三个接口对应的带参数版本(deferred.resolveWith,deferred.rejectWith,deferred.notifyWith),而三个接口又是订阅列表(Callbacks)的fireWith方法。由此可见,要能非常明白的看懂Deferred的源码,必须对Callbacks的源码非常熟悉,至少要能熟练运用Callbacks。

2. 将deferred的精简版promise中的方法和属性或从到自己身上。()这里的promise也是一种非常巧妙的处理。

构造结束后我们就会得到如下图所示的Deferred对象:

至此,Deferred对象构建完成。

比较发现,promise比deferred少了6个内容推送功能。没有了这几个功能deferred的状态也就不会被改变。

3 Deferred的使用

3.1 done,resolve; fail,reject; progress,notify

[+]view code
var def=$.Deferred();
setTimeout(function () {
console.log('耗时操作……');
def.resolve('本次推送的内容……');
},);
def.done(function () {
console.log(arguments[]);
});

这个过程非常简单,只是对Callbacks的简单包装。

[+]view code

var def=$.Deferred();
setTimeout(function () {
  new Error('不好意思,出错了……');
  def.reject('台风影响,本次推送出错……');
},);
def.done(function () {
  console.log(arguments[]);
}).fail(function () {
  console.log(arguments[]);
});

这里的原理和是那个面的一模一样。

[+]view code

var def=$.Deferred();
setTimeout(function() {
    console.log('正在执行……');
    def.notify('正在执行……');
},);
def.done(function () {
    console.log(arguments[]);
}).fail(function () {
    console.log(arguments[]);
}).progress(function() {
    console.log(arguments[]);
});

3.2 then()

[+]view code

var def=$.Deferred();
setTimeout(function () {
    console.log('正在执行……');
    def.notify('正在执行……');
},);
def.then(function () {
    console.log(arguments[]);
},function () {
    console.log(arguments[]);
},function () {
    console.log(arguments[]);
});

then方法返回一个新的的Deferred对象的精简版promise。在创建该Deferred是传递进去了一个function参数。我们发现,在构建Deferred的工厂方法就有一个func参数,它内部的处理是这样的:

以工厂方法创建的deferred为上下文,以该deferred为参数来调用这个方法。函数执行完后,工厂方法将deferred返回。

然后我们来看这个func具体是怎么设计的,这里的设计的非常之复杂。这里最重要的概念就是作用域链,如果对这些基础知识不熟悉的话,理解起来真的很费劲……

以上面实例代码为例,来分析then的源码:

分析源码不仅可以回顾JavaScript知识,还可以借鉴别人的设计思路,代码书写方式,对提高自身的修养是很有帮助的。

下一章分析jQuery.when的实现……

jQuery.Deferred 源码分析的更多相关文章

  1. jQuery.attributes源码分析(attr/prop/val/class)

    回顾 有了之前的几篇对于jQuery.attributes相关的研究,是时候分析jQuery.attr的源码了 Javascript中的attribute和property分析 attribute和p ...

  2. 移动web app开发必备 - Deferred 源码分析

    姊妹篇  移动web app开发必备 - 异步队列 Deferred 在分析Deferred之前我觉得还是有必要把老套的设计模式给搬出来,便于理解源码! 观察者模式 观察者模式( 又叫发布者-订阅者模 ...

  3. jQuery.queue源码分析

    作者:禅楼望月(http://www.cnblogs.com/yaoyinglong ) 队列是一种特殊的线性表,它的特殊之处在于他只允许在头部进行删除,在尾部进行插入.常用来表示先进先出的操作(FI ...

  4. jQuery.buildFragment源码分析以及在构造jQuery对象的作用

    这个方法在jQuery源码中比较靠后的位置出现,主要用于两处.1是构造jQuery对象的时候使用 2.是为DOM操作提供底层支持,这也就是为什么先学习它的原因.之前的随笔已经分析过jQuery的构造函 ...

  5. 深入分析,理解jQuery.Deferred源码

    前言: 如果你对jQuery.Callback回调对象不了解,或者只掌握其方法,但是没有通过阅读源码理解,可以先阅读 前一章jQuery.Callbacks源码解读二,因为只有完全理解jQuery.C ...

  6. jQuery.access源码分析

    基本理解 jQuery.attr是jQuery.attr,jQuery.prop,jQuery.css提供底层支持,jQuery里一个比较有特色的地方就是函数的重载, 比如attr,有如下几种重载 $ ...

  7. jQuery 2.1.4版本的源码分析

    jQuery 2.1.4版本的源码分析 jquery中获取元素的源码分析 jQuery.each({// 获取当前元素的父级元素 parent: function(elem) { var parent ...

  8. jQuery.Callbacks 源码解读二

    一.参数标记 /* * once: 确保回调列表仅只fire一次 * unique: 在执行add操作中,确保回调列表中不存在重复的回调 * stopOnFalse: 当执行回调返回值为false,则 ...

  9. jQuery 2.0.3 源码分析 Deferred(最细的实现剖析,带图)

    Deferred的概念请看第一篇 http://www.cnblogs.com/aaronjs/p/3348569.html ******************构建Deferred对象时候的流程图* ...

随机推荐

  1. iOS网络基础知识

    iOS网络基础知识 1.一次HTTP请求的完整过程 (1)浏览器或应用发起Http请求,请求包含Http请求Http(请求),地址(url),协议(Http1.1)请求为头部 (2)web服务器接收到 ...

  2. SubversionEdge 变更绑定地址

    接上一篇,由于VS的项目和SubversionEdge是通过固定的IP地址绑定在一起的,所以这就有个问题:如果由于种种原因,IP地址换了,那么绑定就失效了. 此时,VS项目的各种变更无法提交到Subv ...

  3. art虚拟机启动问题分析

    欢迎转载opendevkit文章, 文章原始地址: http://www.opendevkit.com/?e=69 Attempt to invoke native method in non-sta ...

  4. .net 调用php webservice报错404状态解决方法

    添加引用的地址和实例的地址不一致 在程序中将实例的地址重新赋值即可 例子: test t=new test(); t.url=http://www.sdf.com/sdfdsf.php?wsdl

  5. hadoop使用问题

    前提 环境 ubuntu 安装hadoop 已经有一段时间 1.启动的时候提示 Connection reset by peer 这个查看日志,里面有说 ssh里面某个文件的权限太大 这个ssh里修改 ...

  6. FusionCharts简单教程(六)-----如何自定义图表上的工具提示

          所谓图表上的工具提示就是当鼠标放在某个特定的数据块上时所显示的提示信息.如下: 禁用显示工具提示       在默认情况下工具提示功能是显示的,但是有时候我们并不是很想需要这个功能提示功能 ...

  7. alwaysOn为什么不支持分布式事务

    Alwayson是微软从SQL2012开始引入的一种高可用和高性能架构,它既可以实现故障转移,同时又能实现查询分离,是当前SQL server的所有架构中最优秀的一种. 因此,一般我们都会推荐使用Al ...

  8. Android多线程分析之二:Thread的实现

    Android多线程分析之二:Thread的实现 罗朝辉 (http://www.cnblogs.com/kesalin/) CC 许可,转载请注明出处   在前文<Android多线程分析之一 ...

  9. 【译】用jQuery 处理XML--写在前面的话

    用jQuery 处理XML--写在前面的话 用jQuery 处理XML-- DOM(文本对象模型)简介 用jQuery 处理XML--浏览器中的XML与JavaScript 用jQuery 处理XML ...

  10. linux奇技淫巧 4

    压缩解压 tar 即可压缩也可以解压 c 压缩 如果没有z.j参数,则表示,只打包,不压缩. 就说, t 查看 z 以gzip方式压缩 相当于 gzip ?.. j 以bzip方式压缩 bzip2 ? ...