本课难度非常大,看一遍,蛋会疼,第二遍蛋不舒服,第三遍应该貌似懂了。初学者莫来,没必要,这完全就是一个研究。

JSDeferred是日本高手cho45搞出来的,其易用性远胜于Mochikit Deferred,它的实现形态基本上奠定了后来称为Promise/A的范式,是js在异步编程上的一个里程碑作品。

JSDeferred不像Mochikit Deferred那样用数组保存回调,而是用Deferred对象自身作为载体来保存回调。司徒正美说:看懂这个库的代码,对你的能力提升很大,如是我去看了。

Deferred.define();

next(function(){});

上面代码的意思是:创建一个匿名的Deferred实例,并且绑定一个成功时执行的回调方法。它使用next而不是addCallback来添加回调方法。

当然上面的这种方式,会污染全局作用域,比如:next方法就是在全局作用域下。它还有两种无侵入(不污染全局作用域)的写法:

var o = {};   //定义一个对象

Deferred.define(o);   //把Deferred的方法全部赋到o对象上,因为第一个next方法是Deferred上的静态方法,所以o对象上也有next方法了

o.next(function(){})    //然后就可以调用o对象的next方法添加回调函数。

或者直接使用Deferred:

Deferred.next(function(){})    //它会在内部创建一个Deferred实例,然后调用这个实例的next方法,以及你调用其他的方法时,直接就可以用链式方式进行调用,就跟jQuery的链式操作一样。

上面的这三种方式,无论哪个,第一次调用的next方法,其实是一个静态方法,它的作用是提供一个JSDeferred实例,并执行第一个异步操作。

异步操作在JSDeferred中有很多实现方式,如setTimeout,image.onerror,script.onreadystatechange等,那么当调用第一个next时,它的异步操作是执行那种方式实现的异步操作呢?其实它会根据不同的浏览器,选择在这个浏览器中最快的API来执行。我们先来看一个最简单的实现方式,用setTimeout实现第一个next的异步操作。由于第一个next方法是静态方法,所以它的定义是:第一个next方法就是在Deferred构造函数中的next_default方法(有很多方式来实现它的异步操作,这里我们只讲setTimeout是如何实现的)。

Deferred.next_default = function(fun){

  var d = new Deferred();

  var id = setTimeout(function(){

    clearTimeout(id);

    d.call()

  },0);

  d.canceller = function(){

    try{

      clearTimeout(id);

    }catch(e){}

  };

  if(fun){

    d.callback.on = fun;

  }

  return d;

};

我们来看第一次调用next方法时,它所做的操作。首先,new一个新的Deferred,赋给局部变量d。然后弄一个0秒定时器(它的意思是0秒后执行回调方法,但是由于浏览器有最小时钟间隔,因此这个定时器后面的代码会先执行)。这时,给新建的Deferred对象定义canceller属性,如果有传入fun参数(成功时执行的回调函数),就把这个函数加载到新建的Deferred对象的callback.ok(new 出来的Deferred会默认有callback属性,它的值是一个对象,对象里面有ok,ng属性,这里是重写了它的ok属性值)属性上,返回新建的Deferred对象。最后等过了浏览器的最小时钟间隔后,就会立即执行setTimeout里面的回调方法,代码开始时首先清除这个定时器(这时setTimeout就失效了,但是这里无法阻止回调方法里面的代码执行),然后调用新建的Deferred对象的call方法(此方法就会执行next方法中添加的函数)。

第二个调用的next方法,以及后面调用的next方法都是实例方法,也就是Deferred.prototype原型对象中的方法,因为第一次调用next方法时,执行的是Deferred的静态方法next_default,它会返回一个Deferred的实例对象,所以后面的链式调用next方法,其实调用的都是Deferred实例对象的next方法。因此第二个以及后面的next方法跟第一个next的方法完全不同。举个例子:

Deferred.define();

next(function fun1(){alert(1)}).next(function fun2(){alert(2)});   //第一个next方法是Deferred的静态方法next_default,返回一个new出来的Deferred实例对象,然后调用第二个next方法时,其实就是d.next(function fun2(){ alert(2)}),这时第二个next方法就是Deferred的实例方法(Deferred.prototype对象中的方法)。

alert(3);

上面的结果:3,1,2。因为第一个next中添加的函数,会延迟执行(里面有个定时器),所以3先打印出来。第二个next方法,是实例方法,它会新建一个Deferred对象,把它赋给当前Deferred对象的._next属性,它里面没有定时器。所以2不会弹出,只有等定时器结束,弹出1后,当前的Deferred对象会通过._next属性找到新建的Deferred对象,然后执行它的回调函数,弹出2。

我们来看下Deferred构造函数的源码:

function Deferred(){

  return (this instanceof Deferred) ? this.init() : new Deferred();  //new Deferred时,执行上下文必须是Deferred对象,才能初始化,不然就new一个新的Deferred对象再初始化

}

Deferred.ok = function(x) { return x};

Deferred.ng = function(x) { return x};

Deferred.prototype = {

  init:function(){   //new Deferred时,调用的就是init方法,来对Deferred对象进行初始化

    this._next = null;

    this.callback = {   //new出来的Deferred对象,会有默认的callback属性,它的值是一个json对象,json对象中有ok属性和ng属性,ok属性其实就是成功时,回调的方法,ng是失败时,回调的方法。当你在next方法中添加回调函数时,会覆盖这ok,ng这两个属性值。

      ok:Deferred.ok,

      ng:Deferred.ng

    };

    return this;

  },

  call:function(val){

    return this._fire("ok",val);   //这就是上面的d.call方法,也就是定时器中的回调方法执行时,调用的方法。定时器一结束,就立即执行。call方法,会调用next中添加的函数。

  },

  _fire:function(okng,value){

    var next = "ok";

    try{

      value = this.callback[okng].call(this,value);   //执行next中添加的函数,如果此函数抛出错误,就会执行catch中的代码,上面的例子,其实就是弹出1,不会执行catch中的代码。(这里的意思是,如果这个next中添加的函数抛出错误,那么它之后的那个next中的异步操作会调用ng的函数,也就是失败时执行的函数)

    }catch(e){

      next = "ng";

      value = e;

    }

    if(value instanceof Deferred){   //如果next添加的函数返回值是Deferred对象d1,就把当前Deferred对象的_next属性值赋给d1的next属性。

      value._next = this._next;

    }else{   //如果返回值不是Deferred,就是上面例子的结果

      if(this._next){     //判断当前的Deferred对象是否有._next属性(第二个next方法创建的Deferred实例对象),如果有,就调用它的_fire方法,上面的例子是有的,所以会调用第二个next方法添加的回调函数。

        this._next._fire(next,value);     //如果当前Deferred对象的ok回调函数报错(next="ng"),那么下一个Deferred对象(下一个next新建的Deferred对象)调用的是名为ng的回调函数。而且当前回调函数返回的值可以作为下一个next的回调函数中的参数值。

      }

    }

    return this;

  },

  next:function(fun){    //这就是第二次以及之后调用next的执行方法,是Deferred对象的实例方法,在Deferred的原型上

    return this._post("ok",fun);

  },

  _post:function(okng,fun){    //第二次调用next以及之后调用next,都会返回一个新的Deferred实例对象,而且next中添加的方法,会赋给新创建的Deferred实例对象的callback.ok属性。而且新创建的Deferred实例对象会是当前Deferred对象的._next属性值

    this._next = new Deferred();

    this._next.callback[okng] = fun;

    return this._next;

  },

  error : function(fun){   //next中的回调方法抛出错误时,就会执行下一个error中的回调方法。比如:next(function(){  抛出错误  }).error(function(){ 抛出错误的方法执行后,就会执行这里 })

    return this._post("ng",fun);

  }

}

上面代码最好是放在window.onload中进行执行,因为它的next静态方法(异步操作),如果是用image.onerror实现时,会调用document.body.appendChild(image),如果这时DOM树还没建好,那就会出错。

上面的例子中,我们可以知道,fun1和fun2是同时被添加到执行队列的,也就是说fun1执行完,马上就执行fun2,而如果我们需要延迟执行fun2,就可以这样操作:

Deferred.next(function fun1(){alert(1)}).wait(1).next(function fun2(){alert(2)});  这样fun2会在fun1执行结束一秒后,才会添加到执行队列进行执行。

观看JSDeferred的源码,Deferred原型对象中没有wait方法,只有一个同名的静态方法,那么它是怎么被加载到Deferred原型上的呢?

wait静态方法是通过register方法进行函数转换,并绑定到原型上的,下一课将进行讲解。

加油!

第三十课:JSDeferred详解1的更多相关文章

  1. NeHe OpenGL教程 第三十课:碰撞检测

    转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...

  2. Hadoop集群搭建安装过程(三)(图文详解---尽情点击!!!)

    Hadoop集群搭建安装过程(三)(图文详解---尽情点击!!!) 一.JDK的安装 安装位置都在同一位置(/usr/tools/jdk1.8.0_73) jdk的安装在克隆三台机器的时候可以提前安装 ...

  3. Linux防火墙iptables学习笔记(三)iptables命令详解和举例[转载]

     Linux防火墙iptables学习笔记(三)iptables命令详解和举例 2008-10-16 23:45:46 转载 网上看到这个配置讲解得还比较易懂,就转过来了,大家一起看下,希望对您工作能 ...

  4. spring事务详解(三)源码详解

    系列目录 spring事务详解(一)初探事务 spring事务详解(二)简单样例 spring事务详解(三)源码详解 spring事务详解(四)测试验证 spring事务详解(五)总结提高 一.引子 ...

  5. python selenium 三种等待方式详解[转]

    python selenium 三种等待方式详解   引言: 当你觉得你的定位没有问题,但是却直接报了元素不可见,那你就可以考虑是不是因为程序运行太快或者页面加载太慢造成了元素不可见,那就必须要加等待 ...

  6. Window下PHP三种运行方式图文详解,window下的php是不是单进程的?

    Window下PHP三种运行方式图文详解,window下的php是不是单进程的? PHP运行目前为止主要有三种方式: a.以模块加载的方式运行,初学者可能不容易理解,其实就是将PHP集成到Apache ...

  7. OSGi 系列(三)之 bundle 详解

    OSGi 系列(三)之 bundle 详解 1. 什么是 bundle bundle 是以 jar 包形式存在的一个模块化物理单元,里面包含了代码,资源文件和元数据(metadata),并且 jar ...

  8. “全栈2019”Java第一百零三章:匿名内部类详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  9. “全栈2019”Java第三十章:数组详解(下篇)

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  10. C++的三种继承方式详解以及区别

    目录 目录 C++的三种继承方式详解以及区别 前言 一.public继承 二.protected继承 三.private继承 四.三者区别 五.总结 后话 C++的三种继承方式详解以及区别 前言 我发 ...

随机推荐

  1. CentOS6.5上golang环境配置

    CentOS6.5上golang环境配置 一.下载和解压go环境包 >>cd /usr/local/src/ >>wget -c http://golangtc.com/sta ...

  2. sql date时间加减几天几小时

    //时间转成年月日时分秒select date_format(now(),'%Y%m%d%H%i%S')//时间转成年月日select date_format(now(),'%Y%m%d')//去年此 ...

  3. c# 参数传递

    c#类型有值类型与引用类型. 无论哪种类型的变量,作为方法的参数进行传递时,默认是以"值传递"方式来传递的. 传递给方法的形参,在执行时都会新创建一个局部变量,然后接受实参的值, ...

  4. django安装和卸载

    一.pip安装 安装: apt-get update apt-get install python-pip pip install django==1.4.20 卸载: pip uninstall d ...

  5. [转]让Windows Server 2008 + IIS 7+ ASP.NET 支持10万并发请求

    本文转自:http://www.cnblogs.com/dudu/archive/2009/11/10/1600062.html 今天下午17点左右,博客园博客站点出现这样的错误信息: Error S ...

  6. 编写JS代码的“use strict”严格模式及代码压缩知识

    Javascript的语法比较松散,大家对该门语言的印象可能是“简单”,我认为这恰恰相反.使用严格模式能防止你写出粗制滥造的语法代码来.应用了严格模式后尽管控制台报告的某些错误需要很大精力排除,但是从 ...

  7. codeforces 484B B. Maximum Value(二分)

    题目链接: B. Maximum Value time limit per test 1 second memory limit per test 256 megabytes input standa ...

  8. python中的深拷贝和潜拷贝

    >>> a = ['ace',['age',10]] >>> b = a[:] >>> c = list(a) >>> for ...

  9. PHP基本知识

    PHP是以一种嵌入在HTML代码中的脚本语言,它由服务器负责解释,可以用于管理动态内容.支持数据库.处理会话跟踪.甚至构建整个电子商务站点. PHP支持许多流行.非流行的数据库,包括MySQL.Pos ...

  10. CSU 1081 集训队分组

    题意:有n个学生,比了一场比赛,但是榜单看不到了.现在告诉你m段信息,每段信息的内容是(a,b),表示a的排名比b的高.问你能不能根据这些信息得出这场比赛的前k名. 思路:用拓扑排序找出一组符合k个人 ...