首页 > 网站 > WEB开发 > 正文

jQuery源码学习:Deferred Object

2024-04-27 15:14:45
字体:
来源:转载
供稿:网友

本文所有讨论均基于jQuery版本3.1.1,官网http://jquery.com/。

一.Deferred Object

1. 简介和创建

详见API:http://api.jquery.com/jQuery.Deferred/。jQuery Deferred是”based onCommonJS PRomises/A design”,并不完全等同于ES6的Promise或者浏览器/JS引擎实现的原生Promise或是各类Promise库。

jQuery.Deferred()工厂函数创建一个新的deferred object并且返回这个deferred:

jQuery.extend({    Deferred: function (func) {        var deferred = {};              // ...             // 实现deferred object的对象方法             // ...         return deferred;    }})

2. deferred与闭包

由于dererred的每个对象方法method都访问了外部函数jQuery.Deferred()中的局部变量,因此jQuery.Deferred()在执行完毕后,其活动对象activation object不会被销毁,而是被保存在deferred的每个method的[[Scope]]属性中(仅保存指针)。这样deferred的method在执行时就可以通过作用域链scope chain访问到deferred的“私有变量”(参见javascript高级程序设计第三版7.4章)。

这里的“私有变量”不是静态的,即每次通过调用jQuery.Deferred()得到的deferred都有自己的一份“私有变量”。

注1:这是在jQuery“块级作用域”(参见Javascript高级程序设计第三版7.3章)里定义的局部变量:

var%20%20%20%20version%20=%20"3.1.1",%20%20%20%20//%20Define%20a%20local%20copy%20of%20jQuery%20%20%20%20jQuery%20=%20function%20(selector,%20context)%20{...},

注2:这是在jQuery“块级作用域”里,通过下面的语句expose到全局环境下的变量:

%20%20%20%20window.jQuery%20=%20window.$%20=%20jQuery;

注3:jQuery.Deferred()返回的deferred%20object是jQuery.Deferred()的一个局部变量。

注4:deferred.promise()返回这个promise%20object。

3.%20deferred的私有变量

3.1.%20state

取三种值”pending”,”resolved”,”rejected”之一,分别对应Spec里的%20pending,fulfilled,%20or%20rejected;

可以通过.state()方法读取;

初始状态是”pendinig”。

3.2%20promise

可以通过.promise()读取。和deferred一样是jQuery.Deferred()的局部变量:

var%20promise%20=%20{%20%20%20%20...%20%20%20%20promise:%20function%20(obj)%20{%20%20%20%20%20%20%20%20return%20obj%20!=%20null%20?%20jQuery.extend(obj,%20promise)%20:%20promise;%20%20%20%20}},%20%20%20%20deferred%20=%20{};%20%20%20%20…%20%20%20//%20向deferred添加resolve,reject,notify,resolveWith,rejectWith和notifyWith方法%20%20%20%20//%20Make%20the%20deferred%20a%20promise%20%20%20%20promise.promise(deferred);

从上面jQuery.Deferred()的代码可以看出,deferred对象copy了promise所有的方法;deferred和promise的方法指向相同的函数实例,因而deferred和deferred.promise()拥有同一份“私有变量”:

promise没有可以改变state的方法,即promise没有.resolve()/.resolveWith()/.reject()/.rejectWith()/.notify()/.notifyWith()方法。这样返回deferred.promise()给其它代码就可以确保其它代码不会改变deferred的状态或触发回调函数的执行。

3.3%20tuples

var%20tuples%20=%20[%20%20%20%20%20%20%20%20//%20action,%20add%20listener,%20callbacks,%20%20%20%20%20%20%20%20//%20...%20.then%20handlers,%20argument%20index,%20[final%20state]%20%20%20%20%20%20%20%20["notify",%20"progress",%20jQuery.Callbacks("memory"),%20%20%20%20%20%20%20%20%20%20%20%20jQuery.Callbacks("memory"),%202],%20%20%20%20%20%20%20%20["resolve",%20"done",%20jQuery.Callbacks("once%20memory"),%20%20%20%20%20%20%20%20%20%20%20%20jQuery.Callbacks("once%20memory"),%200,%20"resolved"],%20%20%20%20%20%20%20%20["reject",%20"fail",%20jQuery.Callbacks("once%20memory"),%20%20%20%20%20%20%20%20%20%20%20%20jQuery.Callbacks("once%20memory"),%201,%20"rejected"]%20%20%20%20],

tuples数组存放了6个callback%20list%20object,为讨论方便,我们命名

 %20 %20progress_callbacks%20= tuples[0][2],%20 %20progress_handlers%20= tuples[0][3]

 %20 %20fulfilled_callbacks%20= tuples[1][2],%20 %20 %20 %20fulfilled_handlers%20= tuples[1][3]

 %20 %20rejected_callbacks%20= tuples[2][2],%20 %20 %20rejected_handlers%20= tuples[2][3]

3.3.1%20callback%20list

以fulfilled_callbacks即tuples[1][2]为例,它是由jQuery.Callbacks(“once%20memory”)创建的一个callback%20list,这个callbacklist的.add()方法把回调函数加入到list中,而.fireWith()方法将导致list里的所有回调函数以类似于”先进先出”的顺序执行。

3.3.2%20“once”%20flag

fulfilled_callbacks创建时带有两个flag:”once”和”memory”。“once”表示这个callback%20list只能通过.fireWith()或.fire()的调用被fire一次,以后再次调用这个callback%20list的.fireWith()或.fire()没有任何效果。

注意progress_callbacks和progress_handlers创建时没有”once”这个flag,也就是说这两个callback%20list可以被多次fire,每次fire时list里的所有回调函数顺序执行。

3.3.3%20“memory”%20flag

callback%20list在fire时的context和arguments将被“记忆”下来,这样在.fireWith()之后调用.add()的话,新加入的回调函数将马上用“记住”的context和arguments执行。

%20%20%20%20fulfilled_callbacks%20=%20jQuery.Callbacks("once%20memory");%20%20%20%20fulfilled_callbacks.add(fn1);%20%20%20%20fulfilled_callbacks.add(fn2);%20%20%20%20fulfilled_callbacks.fireWith(context,%20args);%20%20%20//%20fn1先执行,即fn1.apply(context,args);然后fn2执行fn2.apply(context,args)%20 %20%20%20%20fulfilled_callbacks.fireWith(context,%20args);%20%20%20//%20没有任何效果%20%20%20%20fulfilled_callbacks.add(fn3);%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20//%20fn3马上执行,即fn3.apply(context,%20args)

3.4.%20对象方法与私有变量

Deferred的对象方法

这个方法会调用

对状态的影响

效果

.done()

fulfilled_callbacks.add()

不改变state

把回调函数加入到fulfilled_callbacks中

.resolve()/.resolveWith()

fulfilled_callbacks.fireWith()

state%20=%20“resovled”

fulfilled_callbacks中的所有回调函数被执行注1

fulfilled_handlers中的所有回调函数被执行

.fail()

rejected_callbacks.add()

不改变state

把回调函数加入到rejected_callbacks中

.reject()/.rejectWith()

rejected_callbacks.fireWith()

state%20=%20“rejected”

rejected_callbacks中的所有回调函数被执行注1

rejected_handlers中的所有回调函数被执行

.progress()

progress_callbacks.add()

不改变state

把回调函数加入到progress_callbacks中

.notify()/.notifyWith()

progress_callbacks.fireWith()

不改变state

progress_callbacks中的所有回调函数被执行

progress_handlers中的所有回调函数被执行

.then()/.catch()

fulfilled_handlers.add()

rejected_handlers.add()

progress_handlers.add()

不改变state

把回调函数加入到fulfilled_handlers中

把回调函数加入到rejected_handlers中

把回调函数加入到progress_handlers中

注1:这两个方法如果重复执行的话,只有第一次的调用会有效果,原因见3.3.2。

4.%20链式调用

除了.state()和.promise()外,deferred的其它方法均返回一个deferred%20object,这样deferred就可以支持链式调用,例如.always():

always:%20function%20()%20{%20%20%20%20deferred.done(arguments).fail(arguments);%20%20%20%20return%20this;},

调用.always()方法相当于同时向fulfilled_callbacks和rejected_callbacks中添加了一个同样的回调函数,这样无论这个deferred是被.resolve()/.resolveWith()还是.reject()/.rejectWith(),这个回调函数always将被执行。

需要注意的是大多数方法均返回this指针,也就是方法在哪个deferred上被调用,就返回哪个deferred。而.then()和.catch()方法返回的是一个新的deferred的promise。

%20%20%20%20dfd1%20=%20jQuery.Deferred();%20%20%20%20dfd1.done(fn)%20===%20dfd1;%20%20%20%20%20%20%20%20%20%20%20%20//true%20%20%20%20dfd1.then(fn)%20===%20dfd1.promise();%20//false

二.Control%20Flow

1.%20同步执行流程

这里的同步执行流程指的是在同一个event%20loop里被顺序执行的代码。由于是在一个event%20loop里单线程运行,这些代码是同步执行的。例如deferred.resolve()/.resolveWith()被调用时,代码总是按照下图中箭头指定的顺序执行:

注1:也就是说,在deferred.resolve()/.resolveWith()之后,再去调用deferred.reject()/.rejectWith()将没有任何效果。反之亦然。

注2:如果.notify()/.notifyWith()被调用过,那么.resolve()/.resolveWith()调用时不会disable掉progress_callbacks.add(),这样仍然可以通过progress()方法加入progress的回调函数,而且这样加入的回调函数会被立即执行。

    dfd1 = jQuery.Deferred();    dfd1.progress(function () {        console.log("progress1")    });    dfd1.notify();                    // console log "progress1"    dfd1.resolve();    dfd1.progress(function () {      // console log "progress2" immediately, i.e. before dfd1.progress() returns        console.log("progress2")    });

当然如果.notify()/.notifyWith()没有被调用过,那么.resolve()/.resolveWith()调用后就无法通过.progress()方法加入progress的回调函数了。

2. .then()

2.1 .then()同步执行流程

jQuery.Deferred()调用时可以带一个参数,这个参数指向一个函数func,在jQuery.Deferred()最后返回deferred之前会调用这个函数func。

Deferred: function (func) {    …    if (func) {        func.call(deferred, deferred);    }    // All done!    return deferred;},

.then()在最终返回新的deferredobject(其实是promise)之前,就调用了一个函数func:

then: function (onFulfilled, onRejected, onProgress) {          …return jQuery.Deferred(func).promises();}

.then()方法的同步执行流程如下:

用文字描述的话,deferred.then(onFulfilled)方法被调用时主要完成了两件“任务”:

a. 把“包裹”了真正的回调函数onFulfilled的一个函数加入了deferred的“私有变量”fulfilled_handlers当中;

b. 产生了一个新的deferredobject即newDefer,并最终返回了newDefer.promise()。

当然这只是逻辑上的划分,实际运行时.then()是同步执行的:“任务”b的前半段先执行(得到了newDefer),接下来“任务”a执行(需要newDefer作为实参),最后“任务”b的后半段执行,就如上图所示。

2.2 加入到fulfilled_handlers中的函数

在上图右侧代码部分,tuples[1][3]就是fulfilled_handlers,它的.add()方法将这个方法的参数加入到fulfilled_handlers中,也就是把执行resolve()函数得到的返回值加入到fulfilled_handlers中。而resolve()函数返回一个匿名的内部函数,所以,加入到fulfilled_handlers中的函数是执行resolve()函数返回的(resolve()函数的)内部函数。

function resolve(depth, deferred, handler, special) {    return function () {            …    };}

请注意这个resolve()函数本身是.then()方法的内部函数,并不是.resolve()方法。

 

3. 异步执行

3.1 异步的触发

deferred.resolve()/resolveWith()执行时,会调用fulfilled_handlers,fire(),从而运行已加入到fulfilled_handlers中的函数。从上一节可知,此时运行的就是resolve()的内部匿名函数,如下:

function () {        var that = this,            args = arguments,            mightThrow = function () {…},            // Only normal processors (resolve) catch and reject exceptions            process = special ?                mightThrow :                function () {                    try {                        mightThrow();                    } catch (e) {…}                  }                };        // Support: Promises/A+ section 2.3.3.3.1        // https://promisesaplus.com/#point-57        // Re-resolve promises immediately to dodge false rejection from        // subsequent errors        if (depth) {            process();        } else {…            window.setTimeout(process);        }    };}

这个函数定义了两个(对于它而言的)内部函数process()和mightThrow(),并在最后调用了window.setTimeout(),这样process()作为定时器的回调函数被异步执行。

3.2 异步执行时的作用域链

3.3 process()的执行

3.3.1 fulfilled并且onFulfilledhandler返回了non-thenable

这种情况下,process()主要完成了两个“任务”:

a. 运行先前.then()调用中传入的handler即上图中的onFulfilled,返回值赋给returned; 

    returned = handler.apply(that, args);

这里handler是.then()调用中传入的onFulfilled,that是.resolveWith()时传入的context,args是.resolveWith()时传入的args,参考上图。

b. 传递状态,这是通过执行语句deferred.resolveWith(that, args)来完成的。这里deferred其实是.then()产生的newDefer,that是.resolveWith()时传入的context,args是上一个任务中apply onFulfilled的返回returned,参考上图。

3.3.2 fulfilled而且onFulfilledhandler返回了thenable

这种情况下,第一个任务和前一种情况是一样的:

a. 运行先前.then()调用中传入的handler即上图中的onFulfilled,返回值赋给returned;

b. 递归。因为returned不是一个“可传递”的状态(而是一个有着then方法的对象),无法直接传递给newDefer,只有等到returned这个thenable产生了一个状态,并且这个状态是“可传递”的(不是另外一个thenable),我们才能继续状态的传续给newDefer。

这种情况下,下面的代码将执行:

then.call(    returned,    resolve(maxDepth, deferred, Identity, special),    resolve(maxDepth, deferred, Thrower, special),    resolve(maxDepth, deferred, Identity,        deferred.notifyWith));

这里then是returned.then,deferred就是newDefer,Identify函数如下:

function Identity(v) {    return v;}

为方便起见,我们把上面这个call()调用中的第二个参数resolve()的返回结果叫做identity_wrapper_handler,那么上面这个call()大致等效于(仅考虑fulfilled情况)

    returned.then(identity_wrapper_handler);

step 1. identity_wrapper_handler将被异步调用,即在process()中会apply identity_wrapper_handler

step 2. identity_wrapper_handler是调用resolve(maxDepth, deferred, Identity, special)返回的函数,这里maxDepth=1,deferred就是newDefer,special是undefined,可参考上面的作用域链图。这样apply identity_wrapper_handler会导致process()再次被运行。

step 3. 但是这次handler是Identity,所以这次process()会apply Identity,根据上面Identify函数的定义,apply Identity将会返回returned被resolve时的状态。换句话说,如果returned.reslove(y),那么apply Identity将会返回y。

step 4. 这时我们又回到了类似于process()第一次执行时情况:如果y不是一个thenable,那么状态可以传递给newDefer;否则就重复3.3.2.b的“任务”,这样就形成了递归。

对照规范https://promisesaplus.com/来看,3.3.2.b描述的就是规范中的”Promise Resolution Procedure”。如果认为deferred就是规范中的promise,returned就是规范中的x,returned.reslove(y)里的y就是规范中的y,那么3.3.2.b就实现了规范2.3所描述的从[[Resolve]](promise,x)到[[Resolve]](promise,y)。

从process()调用来看,先后发生了三次process()调用,每次process()调用时变量所指向/引用的对象如下表:

 

每次process()运行都先执行语句returned = handler.apply(that, args);

作用域链

handler

that

args

第一次运行时

onFulfilled

deferred.resolveWith(context, args)中的context

deferred.resolveWith(context, args)中的args

指向deferred.then()运行后形成的closures

第二次运行时

identity_wrapper_handler

returned.resolveWith(context, args)中的context

returned.resolveWith(context, args)中的args

指向returned.then()运行后形成的closures

第三次运行时

Identity

returned.resolveWith(context, args)中的context

returned.resolveWith(context, args)中的args

同第一次,即也指向deferred.then()运行后形成的closures

实现中还有一些细节,比如防止self thenable resolution,在follow thenable时避免false rejections,遵从协议的细节处理exceptions等等。

三.Deferred的应用

比如jQuery中ready方法还有Ajax等等都用到了deferred object。


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表