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

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. zsh

    一.简介 Zsh 也许是目前最好用的 shell,是 bash 替代品中较为优秀的一个.   二.优点 1)补全 zsh 的命令补全功能非常强大,可以补齐路径,补齐命令,补齐参数等. 按下 tab 键 ...

  2. java 基础

    一 4类8种基本数据类型 逻辑型   - boolean 文本型   - char 整数型   - byte short int long 浮点数型 - float double ☆java各整数类型 ...

  3. puppet学习笔记(二)

    在puppet安装完成之后我们就可以动手开始第一个puppet实验了,此实验就以批量推送文件为例吧. 1.获取module路径 这里的module就是指一个模块,可以把puppet想象成一个个项目的部 ...

  4. Excel Sheet Column Number

    Related to question Excel Sheet Column Title Given a column title as appear in an Excel sheet, retur ...

  5. BI Project Managerment

    Design doc is the meta data of the code. The project management plan is crucial to your project sinc ...

  6. 【读书笔记《Android游戏编程之从零开始》】11.游戏开发基础(SurfaceView 游戏框架、View 和 SurfaceView 的区别)

    1. SurfaceView 游戏框架实例 实例效果:就是屏幕上的文本跟着点击的地方移动,效果图如下: 步骤: 新建项目“GameSurfaceView”,首先自定义一个类"MySurfac ...

  7. 如何显示隐藏的Administrator账户

    在Windows XP中,Administrator帐户是终极管理员,如果你创建了其他管理员帐户,那么该帐户就会从欢迎屏幕上被隐藏.这里需要注意的是,仅仅是从欢迎屏幕上被隐藏,该帐户仍然存在.   如 ...

  8. UESTC 913 握手 Havel定理+优先队列

    给定一个非负整数序列{dn},若存在一个无向图使得图中各点的度与此序列一一对应,则称此序列可图化.进一步,若图为简单图,则称此序列可简单图化. 此题因为是无自环无重边,所以是简单图.用判定简单图可图化 ...

  9. 定制你的Unity编辑器

    Unity的编辑器可以通过写脚本进行界面定制,包括添加功能菜单,今天写游戏Demo用到了记录一下. 为Unity添加子菜单 示例程序 [AddComponentMenu("Defend Ho ...

  10. View (二) 自定义属性 自定义属性的格式详解

    自定义属性格式一共有十种: 1. reference:参考某一资源ID. 2. color:颜色值. 3. boolean:布尔值. 4. dimension:尺寸值. 5. float:浮点值. 6 ...