JS魔法堂:jsDeferred源码剖析

页面导航:首页 > 网络编程 > JavaScript > JS魔法堂:jsDeferred源码剖析

JS魔法堂:jsDeferred源码剖析

来源: 作者: 时间:2016-02-05 11:06 【

一、前言 最近在研究Promises A+规范及实现,而Promise A+规范的制定则很大程度地参考了由日本geek cho45发起的jsDeferred项目(《JavaScript框架设
一、前言                            
 
   最近在研究Promises/A+规范及实现,而Promise/A+规范的制定则很大程度地参考了由日本geek cho45发起的jsDeferred项目(《JavaScript框架设计》提供该资讯,再次感谢),追本溯源地了解jsDeferred是十分有必要的,并且当你看过官网(http://cho45.stfuawsc.com/jsdeferred/)的新手引导后就会有种不好好学学就太可惜的感觉了,而只看API和使用指南是无法满足我对它的好奇心的,通过解读读透它的设计思想才是根本。
 
  本文部分内容将和《JS魔法堂:剖析源码理解Promises/A》中的内容作对比来讲解。
 
  由于内容较多,特设目录一坨
 
    二、jsDeferred与Promises/A的核心区别
 
    三、从API说起
 
  四、细说功能实现
 
 
 
二、jsDeferred与Promises/A的核心区别              
 
  jsDeferred的特点:
 
  ①. 内部通过单向链表结果存储 成功事件处理函数、失败事件处理函数 和 链表中下一个Deferred类型对象;
 
  ②. Deferred实例内部没有状态标识(也就是说Deferred实例没有自定义的生命周期);
 
  ③. 由于Deferred实例没有状态标识,因此不支持成功/失败事件处理函数的晚绑定;
 
  ④. Deferred实例的成功/失败事件是基于事件本身的触发而被调用的;
 
  ⑤. 由于Deferred实例没有状态标识,因此成功/失败事件可被多次触发,也不存在不变值作为事件处理函数入参的说法;
 
  Promises/A的特点:
 
  ①. 内部通过单向链表结果存储 成功事件处理函数、失败事件处理函数 和 链表中下一个Promise类型对象;
 
  ②. Promise实例内部有状态标识:pending(初始状态)、fulfilled(成功状态)和rejected(失败状态),且状态为单方向移动“pending->fulfilled","pending->rejected";(也就是Promse实例存在自定义的生命周期,而生命周期的每个阶段具备不同的事件和操作)
 
  ③. 由于Promise实例含状态标识,因此支持事件处理函数的晚绑定;
 
  ④. Promise实例的成功/失败事件函数是基于Promise的状态而被调用的。
 
  
 
  核心区别
 
      Promises调用成功/失败事件处理函数的两种流程:
 
         ①. 调用resolve/reject方法尝试改变Promise实例的状态,若成功改变其状态,则调用Promise当前状态相应的事件处理函数;(类似于触发onchange事件)
 
         ②. 通过then方法进行事件绑定,若Promise实例的状态不是pending,则调用Promise当前状态相应的事件处理函数。
 
         由上述可以知道Promises的成功/失败事件处理函数均基于Promise实例的状态而被调用,而非成功/失败事件。
 
      jsDeferred调用成功/失败事件处理函数的流程:
 
         ①. 调用call/fail方法触发成功/失败事件,则调用相应的事件处理函数。
 
         因此jsDeferred的是基于事件的。
 
 
 
三、从API说起                            
 
  下列内容均为大概介绍API接口,具体用法请参考官网。
 
  1. 构造函数
 
      Deferred ,可通过 new Deferred() 或 Deferred() 两种方式创建Deferred实例。
 
  2. 实例方法
 
   Deferred next({Function} fn) ,绑定成功事件处理函数,返回一个新的Deferred实例。
 
       Deferred error({Function} fn) ,绑定失败事件处理函数,返回一个新的Deferred实例。
 
       Deferred call(val*) ,触发成功事件,返回一个新的Deferred实例。
 
   Deferred fail(val*) ,触发失败事件,返回一个新的Deferred实例。
 
  3. 静态属性
 
       {Function} Deferred.ok ,默认的成功事件处理函数。
 
   {Function} Deferred.ng ,默认的失败事件处理函数。
 
       {Array} Deferred.methods ,默认的向外暴露的静态方法。(供 Deferred.define方法 使用)
 
  4. 静态方法
 
       {Function}Deferred Deferred.define(obj, list) ,暴露list制定的静态方法到obj上,obj默认是全局对象。
 
       Deferred Deferred.call({Function} fn [, arg]*) ,创建一个Deferred实例并且触发其成功事件。
 
       Deferred Deferred.next({Function} fn) ,创建一个Deferred实例并且触发其成功事件,其实就是无法传入参到成功事件处理函数的 Deferred.call() 。
 
       Deferred Deferred.wait(sec) ,创建一个Deferred实例并且等sec秒后触发其成功事件。
 
   Deferred Deferred.loop(n, fun) ,循环执行fun并且上一个fun,最后一个fun的返回值将作为Deferred实例的成功事件处理函数的入参。
 
       Deferred Deferred.parallel(dl) ,将dl中非Deferred对象转换为Deferred对象,然后并行触发dl中的Deferred实例的成功事件,当
 
所有Deferred对象均调用了成功事件处理函数后,返回的Deferred实例则触发成功事件,并且所有返回值将被封装为数组作为Deferred实例的成功事件处理函数的入参。
 
       Deferred Deferred.earlier(dl) ,将dl中非Deferred对象转换为Deferred对象,然后并行触发dl中的Deferred实例的成功事件,当
 
其中一个Deferred对象调用了成功事件处理函数则终止其他Deferred对象的触发成功事件,而返回的Deferred实例则触发成功事件,并且那个被调用的成功事件处理函数的返回值为Deferred实例的成功事件处理函数的入参。
 
       Boolean Deferred.isDeferred(obj) ,判断obj是否为Deferred类型。
 
   Deferred Deferred.chain(args) ,创建一个Deferred实例一次执行args的函数
 
   Deferred Deferred.connect(funo, options) ,将某个函数封装为Deferred对象
 
   Deferred Deferred.register(name, fn) ,将静态方法附加到Deferred.prototype上
 
   Deferred Deferred.retry(retryCount, funcDeferred, options) ,尝试调用funcDeffered方法(返回值类型为Deferred)retryCount,直到触发成功事件或超过尝试次数为止。
 
   Deferred Deferred.repeat(n, fun) ,循环执行fun方法n次,若fun的执行事件超过20毫秒则先将UI线程的控制权交出,等一会儿再执行下一轮的循环。
 
   jsDeferred采用DSL风格的API设计,语义化我喜欢啊!
 
四、细说功能实现                          
 
  1. 基础功能部分
 
    1.1. 构造函数
 
复制代码
function Deferred () { return (this instanceof Deferred) ? this.init() : new Deferred() }
// 默认的成功事件处理函数
Deferred.ok = function (x) { return x };
// 默认的失败事件处理函数
Deferred.ng = function (x) { throw  x };
Deferred.prototype = {
    // 初始化函数
    init : function () {
    this._next    = null;
    this.callback = {
        ok: Deferred.ok,
        ng: Deferred.ng
    };
    return this;
}};
复制代码
    1.2. call函数
 
复制代码
Deferred.prototype.call = function (val) { return this._fire("ok", val) };
Deferred.prototype._filre = function(okng, value){
  var next = "ok";
  try {
    // 调用当前Deferred实例的事件处理函数
    value = this.callback[okng].call(this, value);
  } catch (e) {
    next = "ng";
    value = e;
    if (Deferred.onerror) Deferred.onerror(e);
  }
  if (Deferred.isDeferred(value)) {
    // 若事件处理函数返回一个新Deferred实例,则将新Deferred实例的链表指针指向当前Deferred实例的链表指针指向,
    // 这样新Deferred实例的事件处理函数就会先与原链表中其他Deferred实例的事件处理函数被调用。
    value._next = this._next;
  } else {
    if (this._next) this._next._fire(next, value);
  }
  return this;
};
复制代码
    1.3. fail函数
 
Deferred.prototype.fail = function (err) { return this._fire("ng", err) };
    1.4. next函数
 
复制代码
Deferred.prototype.next = function (fun) { return this._post("ok", fun) };
Deferred.prototype._post = function (okng, fun) {
    // 创建一个新的Deferred实例,插入Deferred链表尾,并将事件处理函数绑定到新的Deferred上  
    this._next = new Deferred();
    this._next.callback[okng] = fun;
    return this._next;
};
复制代码
    《JS魔法堂:剖析源码理解Promises/A》中的官网实现示例是将事件处理函数绑定到当前的Promise实例,而不是新创的Promise实例。而jsDeferred则是绑定到新创建的Deferred实例上。这是因为Promise实例默认的事件处理函数为undefined,而Deferred是含默认的事件处理函数的。
 
    1.5. error函数
 
Deferred.prototype.error = function (fun) { return this._post("ng", fun) };
  2. 辅助功能部分
 
  jsDeferred的基础功能部分都十分好理解,我认为它的精彩之处在于类方法——辅助功能部分。
 
    2.1. Deferred.define函数实现
 
复制代码
Deferred.define = function (obj, list) {
    if (!list) list = Deferred.methods;
    // 以全局对象作为默认的入潜目标
    // 由于带代码运行在sloppy模式,因此函数内的this指针指向全局对象。若运行在strict模式,则this指针值为undefined。
    // 即使被以strict模式运行的程序调用,本段程序依然以sloppy模式运行使用
    if (!obj) obj = (function getGlobal () { return this })();
    for (var i = 0; i < list.length; i++) {
    var n = list[i];
        obj[n] = Deferred[n];
    }
    return Deferred;
};
复制代码
    当我第一次看新手引导中的示例代码
 
Deferred.define();
next(function(){
  ............
}).next(function(){
  ...............
});
    这不是就和jdk1.5的静态导入 import static一样吗?!两者同样是以入侵的方式将类方法附加到当前执行上下文中,这种导入的方式有人喜欢有人明令禁止(原上下文被破坏,维护性大大降低)。而我则有一个准则,就是导入的类方法足够少(5个左右,反正能看一眼API就记得那种),团队的小伙伴们均熟知这些API,并且仅以此方式导入一个类的方法到当前执行上下文中。其实能满足这些要求的库不多,还不如起个短小精干的类名作常规导入更实际。这里扯远了,我再看看 Deferred.define方法 吧,其实它除了将类方法导入到当前执行上下文,还可以导入到一个指定的对象中(这个方法比较中用!)
 
复制代码
var ctx = {};
Deferred.define(ctx);
ctx.next(function(){
   ..............
}).next(function(){
   .............
});
复制代码
    2.2. Deferred.isDeferred函数实现
 
Deferred.isDeferred = function (obj) {
    return !!(obj && obj._id === Deferred.prototype._id);
};
// 貌似是Mozilla有个插件也叫Deferred,因此不能通过instanceof来检测。cho45于是自定义标志位来作检测,并在github上提交fxxking Mozilla,哈哈!
Deferred.prototype._id = 0xe38286e381ae;
    2.3. Deferred.wait函数实现
 
复制代码
Deferred.wait = function (n) {
    var d = new Deferred(), t = new Date();
    var id = setTimeout(function () {
        // 入参为实际等待毫秒数,由于各的setTimeout均有一个最小精准度参数(IE9+和现代浏览器为4msec,IE5.5~8为15.4msec),因此实际等待的时间一定被希望的长
        d.call((new Date()).getTime() - t.getTime());
    }, n * 1000);
    d.canceller = function () { clearTimeout(id) };
    return d;
}; 
复制代码
    刚看到该函数时我确实有点小鸡冻,我们可以将《JS魔法堂:剖析源码理解Promises/A》的第三节“从感性领悟”下的示例,写得于现实生活的思路更贴近了。
 
复制代码
// 任务定义部分
var 下班 = function(){};
var 搭车 = function(){};
var 接小孩 = function(){};
var 回家 = function(){};
 
// 流程部分
next(下班)
    .wait(10*60)
    .next(下班)
    .wait(10*60) 
    .next(搭车)
    .wait(10*60) 
    .next(接小孩)
    .wait(20*60)
    .next(回家);
复制代码
    2.4. Deferred.next函数实现
 
      该函数可为是真个jsDeferred最出彩的地方了,也是后续其他方法的实现基础,它的功能是创建一个新的Deferred对象,并且异步执行该Deferred对象的call方法来触发成功事件。针对运行环境的不同,它提供了相应的异步调用的实现方式并作出降级处理。
 
Deferred.next = 
    Deferred.next_faster_way_readystatechange ||
    Deferred.next_faster_way_Image ||
    Deferred.next_tick ||
    Deferred.next_default;
      由浅入深,我们先看看使用setTimeout实现异步的 Deferred.next_default方法 (存在最小时间精度的问题)
 
复制代码
Deferred.next_default = function (fun) {
    var d = new Deferred();
    var id = setTimeout(function () { d.call() }, 0);
    d.canceller = function () { clearTimeout(id) };
    if (fun) d.callback.ok = fun;
    return d;
};
复制代码
      然后是针对nodejs的 Deferred.next_tick方法 
 
复制代码
Deferred.next_tick = function (fun) {
    var d = new Deferred();
    // 使用process.nextTick来实现异步调用 
    process.nextTick(function() { d.call() });
    if (fun) d.callback.ok = fun;
    return d;
};
复制代码
      然后就是针对现代浏览器的 Deferred.next_faster_way_Image方法 
 
复制代码
Deferred.next_faster_way_Image = function (fun) {
    var d = new Deferred();
    var img = new Image();
    var handler = function () {
        d.canceller();
        d.call();
    };
    img.addEventListener("load", handler, false);
    img.addEventListener("error", handler, false);
    d.canceller = function () {
        img.removeEventListener("load", handler, false);
        img.removeEventListener("error", handler, false);
    };
    // 请求一个无效data uri scheme导致马上触发load或error事件
    // 注意:先绑定事件处理函数,再设置图片的src是个良好的习惯。因为设置img.src属性后就会马上发起请求,假如读的是缓存那有可能还未绑定事件处理函数,事件已经被触发了。
    img.src = "data:image/png," + Math.random();
    if (fun) d.callback.ok = fun;
    return d;
};
复制代码
      最后就是针对IE5.5~8的 Deferred.next_faster_way_readystatechange方法 
 
复制代码
Deferred.next_faster_way_readystatechange = ((typeof window === 'object') && (location.protocol == "http:") && !window.opera && /\bMSIE\b/.test(navigator.userAgent)) && function (fun) {
    var d = new Deferred();
    var t = new Date().getTime();
    /* 原理:
            由于浏览器对并发请求数作出限制(IE5.5~8为2~3,IE9+和现代浏览器为6),
             因此当并发请求数大于上限时,会让请求的发起操作排队执行,导致延时更严重了。
       实现手段:
            以150毫秒为一个周期,每个周期以通过setTimeout发起的异步执行作为起始,
            周期内的其他异步执行操作均通过script请求实现。
            (若该方法将在短时间内被频繁调用,可以将周期频率再设高一些,如100毫秒)
    */
    if (t - arguments.callee._prev_timeout_called < 150) {
        var cancel = false;
        var script = document.createElement("script");
        script.type = "text/javascript";
        // 采用无效的data uri sheme马上触发readystate变化
        script.src  = "data:text/javascript,";
        script.onreadystatechange = function () {
            // 由于在一次请求过程中script的readystate会变化多次,因此通过cancel标识来保证仅调用一次call方法
            if (!cancel) {
                d.canceller();
                d.call();
            }
        };
        d.canceller = function () {
            if (!cancel) {
                cancel = true;
                script.onreadystatechange = null;
                document.body.removeChild(script);
            }
        };
        // 不同于img元素,script元素需要添加到dom树中才会发起请求
        document.body.appendChild(script);
    } else {
        arguments.callee._prev_timeout_called = t;
        var id = setTimeout(function () { d.call() }, 0);
        d.canceller = function () { clearTimeout(id) };
    }
    if (fun) d.callback.ok = fun;
    return d;
};
复制代码
    2.5. Deferred.call函数实现
 
复制代码
Deferred.call = function (fun) {
    var args = Array.prototype.slice.call(arguments, 1);
        // 核心在Deferred.next
    return Deferred.next(function () {
        return fun.apply(this, args);
    });
};
复制代码
    2.6. Deferred.loop函数实现
 
复制代码
Deferred.loop = function (n, fun) {
    // 入参n类似于中range的效果
    // 组装循环的配置信息
    var o = {
        begin : n.begin || 0,
        end   : (typeof n.end == "number") ? n.end : n - 1,
        step  : n.step  || 1,
        last  : false,
        prev  : null
    };
    var ret, step = o.step;
    return Deferred.next(function () {
        function _loop (i) {
            if (i <= o.end) {
                if ((i + step) > o.end) {
                    o.last = true;
                    o.step = o.end - i + 1;
                }
                o.prev = ret;
                ret = fun.call(this, i, o);
                if (Deferred.isDeferred(ret)) {
                    return ret.next(function (r) {
                        ret = r;
                        return Deferred.call(_loop, i + step);
                    });
                } else {
                    return Deferred.call(_loop, i + step);
                }
            } else {
                return ret;
            }
        }
        return (o.begin <= o.end) ? Deferred.call(_loop, o.begin) : null;
    });
};
复制代码
    上述代码的理解难点在于Deferred实例A的事件处理函数若返回一个新的Deferred实例B,而实例A的Deferred链表中原本指向Deferred实例C,那么当调用实例A的call方法时是实例C的事件处理函数先被调用,还是实例B的事件处理函数先被调用呢?这时只需细读 Deferred.prototype.call方法 的实现就迎刃而解了,答案是先调用实例B的事件处理函数哦!
 
    2.7. Deferred.parallel函数实现
 
复制代码
Deferred.parallel = function (dl) {
    // 对入参作处理
    var isArray = false;
    if (arguments.length > 1) {
        dl = Array.prototype.slice.call(arguments);
        isArray = true;
    } else if (Array.isArray && Array.isArray(dl) || typeof dl.length == "number") {
        isArray = true;
    }
 
    var ret = new Deferred(), values = {}, num = 0;
    for (var i in dl) if (dl.hasOwnProperty(i)) (function (d, i) {
        // 若d为函数类型,则封装为Deferred实例
        // 若d既不是函数类型,也不是Deferred实例则报错哦!
        if (typeof d == "function") 
            dl[i] = d = Deferred.next(d);
        d.next(function (v) {
            values[i] = v;
            if (--num <= 0) {
                // 凑够数就触发事件处理函数
                if (isArray) {
                    values.length = dl.length;
                    values = Array.prototype.slice.call(values, 0);
                }
                ret.call(values);
            }
        }).error(function (e) {
            ret.fail(e);
        });
        num++;
    })(dl[i], i);
 
    // 当dl为空时触发Deferred实例的成功事件
    if (!num) Deferred.next(function () { ret.call() });
    ret.canceller = function () {
        for (var i in dl) if (dl.hasOwnProperty(i)) {
            dl[i].cancel();
        }
    };
    return ret;
};
复制代码
    通过源码我们可以知道parallel的入参必须为函数或Deferred实例,否则会报错哦!
 
    2.8. Deferred.earlier函数实现
 
复制代码
Deferred.earlier = function (dl) {
    // 对入参作处理
    var isArray = false;
    if (arguments.length > 1) {
        dl = Array.prototype.slice.call(arguments);
        isArray = true;
    } else if (Array.isArray && Array.isArray(dl) || typeof dl.length == "number") {
        isArray = true;
    }
    var ret = new Deferred(), values = {}, num = 0;
    for (var i in dl) if (dl.hasOwnProperty(i)) (function (d, i) {
        // d只能是Deferred实例,否则抛异常
        d.next(function (v) {
            values[i] = v;
            // 一个Deferred实例触发成功事件则终止其他Deferred实例触发成功事件了
            if (isArray) {
                values.length = dl.length;
                values = Array.prototype.slice.call(values, 0);
            }
            ret.call(values);
            ret.canceller();
        }).error(function (e) {
            ret.fail(e);
        });
        num++;
    })(dl[i], i);
 
    // 当dl为空时触发Deferred实例的成功事件
    if (!num) Deferred.next(function () { ret.call() });
    ret.canceller = function () {
        for (var i in dl) if (dl.hasOwnProperty(i)) {
            dl[i].cancel();
        }
    };
    return ret;
};
复制代码
    通过源码我们可以知道earlier的入参必须为Deferred实例,否则会报错哦!
 
    2.9. Deferred.chain函数实现
 
复制代码
Deferred.chain = function () {
    var chain = Deferred.next();
    // 生成Deferred实例链表,链表长度等于arguemtns.length
    for (var i = 0, len = arguments.length; i < len; i++) (function (obj) {
        switch (typeof obj) {
            case "function":
                var name = null;
                // 通过函数名决定是订阅成功还是失败事件
                try {
                    name = obj.toString().match(/^\s*function\s+([^\s()]+)/)[1];
                } catch (e) { }
                if (name != "error") {
                    chain = chain.next(obj);
                } else {
                    chain = chain.error(obj);
                }
                break;
            case "object":
                // 这里的object包含形如{0:function(){}, 1: Deferred实例}、Deferred实例
                chain = chain.next(function() { return Deferred.parallel(obj) });
                break;
            default:
                throw "unknown type in process chains";
        }
    })(arguments[i]);
    return chain;
};
复制代码
    2.10. Deferred.connect函数实现
 
复制代码
Deferred.connect = function (funo, options) {
    var target, // 目标函数所属的对象
        func, // 目标函数
        obj; // 配置项
    if (typeof arguments[1] == "string") {
        target = arguments[0];
        func   = target[arguments[1]];
        obj    = arguments[2] || {};
    } else {
        func   = arguments[0];
        obj    = arguments[1] || {};
        target = obj.target;
    }
 
    // 预设定的入参
    var partialArgs       = obj.args ? Array.prototype.slice.call(obj.args, 0) : [];
    // 指出成功事件的回调处理函数位于原函数的入参索引
    var callbackArgIndex  = isFinite(obj.ok) ? obj.ok : obj.args ? obj.args.length : undefined;
    // 指出失败事件的回调处理函数位于原函数的入参索引
    var errorbackArgIndex = obj.ng;
 
    return function () {
        // 改造成功事件处理函数,将预设入参和实际入参作为成功事件处理函数的入参
        var d = new Deferred().next(function (args) {
            var next = this._next.callback.ok;
            this._next.callback.ok = function () {
                return next.apply(this, args.args);
            };
        });
 
        // 合并预设入参和实际入参
        var args = partialArgs.concat(Array.prototype.slice.call(arguments, 0));
        // 打造func的成功事件处理函数,内部将触发d的成功事件
        if (!(isFinite(callbackArgIndex) && callbackArgIndex !== null)) {
            callbackArgIndex = args.length;
        }
        var callback = function () { d.call(new Deferred.Arguments(arguments)) };
        args.splice(callbackArgIndex, 0, callback);
 
        // 打造func的失败事件处理函数,内部将触发d的失败事件
        if (isFinite(errorbackArgIndex) && errorbackArgIndex !== null) {
            var errorback = function () { d.fail(arguments) };
            args.splice(errorbackArgIndex, 0, errorback);
        }
        // 相当于setTimeout(function(){ func.apply(target, args) })
        Deferred.next(function () { func.apply(target, args) });
        return d;
    };
};
复制代码
     如何简化将setTimeout、setInterval、XmlHttpRequest等异步API封装为Deferred对象(或Promise)对象的步骤是一件值思考的事情,而jsDeferred的connect类方法提供了一个很好的范本。
 
    2.11. Deferred.register函数实现
 
复制代码
Deferred.register = function (name, fun) {
    this.prototype[name] = function () {
        var a = arguments;
        return this.next(function () {
            return fun.apply(this, a);
        });
    };
};
 
Deferred.register("loop", Deferred.loop);
Deferred.register("wait", Deferred.wait);
复制代码
    2.12. Deferred.retry函数实现
 
复制代码
Deferred.retry = function (retryCount, funcDeferred, options) {
    if (!options) options = {};
 
    var wait = options.wait || 0; // 尝试的间隔时间,存在最小时间精度所导致的延时问题
    var d = new Deferred();
    var retry = function () {
        // 有funcDeferred内部触发事件
        var m = funcDeferred(retryCount);
        m.next(function (mes) {
                d.call(mes);
            }).
            error(function (e) {
                if (--retryCount <= 0) {
                    d.fail(['retry failed', e]);
                } else {
                    setTimeout(retry, wait * 1000);
                }
            });
    };
    // 异步执行retry方法
    setTimeout(retry, 0);
    return d;
};
复制代码
    2.13. Deferred.repeat函数实现
 
复制代码
Deferred.repeat = function (n, fun) {
    var i = 0, end = {}, ret = null;
    return Deferred.next(function () {
        var t = (new Date()).getTime();
        // 当fun的执行耗时小于20毫秒,则马上继续执行下一次的fun;
        // 若fun的执行耗时大于20毫秒,则将UI线程控制权交出,并将异步执行下一次的fun。
        // 从而降低因循环执行耗时操作使页面卡住的风险。
        do {
            if (i >= n) return null;
            ret = fun(i++);
        } while ((new Date()).getTime() - t < 20);
        return Deferred.call(arguments.callee);
    });
};
Tags:

文章评论

最 近 更 新
热 点 排 行
Js与CSS工具
代码转换工具

<