jQuery源码分析(九) 异步队列模块 Deferred 详解

deferred对象就是jQuery的回调函数解决方案,它解决了如何处理耗时操作的问题,比如一些Ajax操作,动画操作等。(P.s:紧跟上一节:https://www.cnblogs.com/greatdesert/p/11433365.html的内容)

异步队列有三种状态:待定(pending)、成功(resolved)和失败(rejected),初始时处于pending状态

我们可以使用jQuery.Deferred创建一个异步队列,返回一个对象,该对象含有如下操作:

  done(fn/arr)                     ;添加成功回调函数,当异步队列处于成功状态时被调用,参数同:jQuery.Callbacks(flags).add(fn/arr)
  fail(fn/arr)                           ;添加失败回调函数,参数同上
  progress(fn/arr)                         ;添加消息回调函数,参数同上
  then(donefn/arr,failfn/arr,profn/arr)     ;同时添加成功回调函数、失败回调函数和消息回调函数
  always(fn/arr)                       ;添加回调函数到doneList和failList中,即保存两份引用,当异步队列处于成功或失败状态时被调用    ;参数可以是函数、函数列表
  state()                                  ;返回异步队列的当前状态、返回pending、resolved或者rejected
  promise(obj)                           ;如果设置了obj参数对象则为obj对象增加异步队列的方法并返回。如果未设置参数obj,则返回当前Deferred对象的只读副本

如果调用$.Diferred()创建一个异步队列,返回后的对象可以调用如下几个方法:

writer by:大沙漠 QQ:22969969

  resolve(args)                      ;使用指定的参数调用所有的成功回调函数,异步队列进入成功状态,之后就无法再调用
  reject(args)                           ;使用指定的参数调用所有的失败回调函数,异步队列进入失败状态
  notify(args)                              ;使用指定的参数调用所有的消息回调函数
  resolveWith(context,args)               ;使用指定的上下文和参数调用所有的成功回调函数,异步队列进入成功状态
  rejectWith(context,args)               ;使用指定的上下文和参数调用所有的失败回调函数,异步队列进入失败状态
  notifyWith(context,args)               ;使用指定的上下文和参数调用所有的消息回调函数

是不是和上一节介绍的Callbacks的fire和fireWith很像,Defferred内部就是通过通过Callback()回调函数列表来实现的,例如:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="http://libs.baidu.com/jquery/1.7.1/jquery.js"></script>
</head>
<body>
<script>
    function f1(x){console.log(‘f1 ‘+x);}
    function f2(x){console.log(‘f2 ‘+x);}
    function f3(x){console.log(‘f3 ‘+x);}

    var t = $.Deferred();                   //创建异步队列
    t.done(f1).fail(f2).progress(f3);       //添加成功回调函数、失败回调函数和消息列表回调函数。等同于t.then(f1,f2,f3);
    t.notify(‘test‘);                       //触发所有消息函数列表,输出:f3 test
    t.resolve(‘done‘);                      //触发所有成功回调函数,输出:done
    t.reject(‘fail‘);                       //没有输出,触发成功回调函数后失败回调函数列表就会被禁用,反过来也如此
    t.progress(f1);                         //输出:f1 test。这是因为消息回调函数之前已经被调用过,保存了上下文和参数了,如果之前没有调用,这里就没有输出。
</script>
</body>
</html>

输出如下:

源码分析



异步队列内部的实现原理就是通过jQuery.Callbacks定义三个回调函数列表,分别存储成功、失败、消息回调函数,然后返回一个对象,在该对象上设置done、fail和progress,分别对应这三个回调函数列表的add方法,这样就实现分别添加不同状态的回调函数了

$.Deferred是通过jQuery.extend函数直接挂载到jQuery里的,结构如下:

jQuery.extend({

    Deferred: function( func ) {
        var doneList = jQuery.Callbacks( "once memory" ),            //成功回调函数列表
            failList = jQuery.Callbacks( "once memory" ),            //失败回调函数列表
            progressList = jQuery.Callbacks( "memory" ),             //消息回调函数列表  ;这三个回调函数列表就是存储我们添加的触发函数的
            state = "pending",                                       //初始状态:pending
            lists = {                                                //后面的方法会遍历变量list中的属性名来为异步队列添加方法deffred.resolve()、resolveWith()、reject()、rejectWith()、notify()和notifyWith()
                resolve: doneList,
                reject: failList,
                notify: progressList
            },
            promise = {                                                //异步队列的只读副本
                /*略*/
            },
            deferred = promise.promise({}),                                //异步队列
            key;

        for ( key in lists ) {                                            //添加触发成功、失败、消息回调函数的方法,执行后deferred对象就多了resolve()、resolveWith()、reject()、rejectWith()、notify()和notifyWith()方法。
            deferred[ key ] = lists[ key ].fire;
            deferred[ key + "With" ] = lists[ key ].fireWith;
        }

        // Handle state
        deferred.done( function() {                                        //添加三个成功回调函数,分别用于设置状态为成功(resolved),禁用失败列表函数列表和锁定消息回调函数列表
            state = "resolved";
        }, failList.disable, progressList.lock ).fail( function() {        //添加三个失败回调韩素,分别用于设置状态为失败(rejected),禁用成功列表函数列表和锁定消息回调函数列表
            state = "rejected";
        }, doneList.disable, progressList.lock );

        // Call given func if any
        if ( func ) {
            func.call( deferred, deferred );
        }

        // All done!
        return deferred;                                                //返回异步队列deffrred
    },
    //...
})

可以看到$.Deferred最后返回的是deferred这个布局变量,而deferred是promise.promise({})的返回值,我们来看看promise的实现方法:

promise = {                        //异步队列的只读副本
    done: doneList.add,                            //添加成功回调函数,引用对应的回调函数列表的callbacks.add(fn/arr)方法,下同
    fail: failList.add,                            //添加失败回调函数
    progress: progressList.add,                    //添加消息回调函数

    state: function() {                            //返回异步队列的当前状态:pending、resolved、rejected之一
        return state;
    },

    // Deprecated
    isResolved: doneList.fired,
    isRejected: failList.fired,

    then: function( doneCallbacks, failCallbacks, progressCallbacks ) {    //同时添加成功回调函数、失败回调函数和消息回调函数
        deferred.done( doneCallbacks ).fail( failCallbacks ).progress( progressCallbacks );
        return this;
    },
    always: function() {                                                //添加回调函数到doneList和failList中,即保存两份引用,当异步队列处于成功或失败状态时被调用
        deferred.done.apply( deferred, arguments ).fail.apply( deferred, arguments );
        return this;
    },
    pipe: function( fnDone, fnFail, fnProgress ) {                        //过滤当前异步队列的状态和参数,并返回一个新的异步队列的副本,一般用不到
        return jQuery.Deferred(function( newDefer ) {
            jQuery.each( {
                done: [ fnDone, "resolve" ],
                fail: [ fnFail, "reject" ],
                progress: [ fnProgress, "notify" ]
            }, function( handler, data ) {
                var fn = data[ 0 ],
                    action = data[ 1 ],
                    returned;
                if ( jQuery.isFunction( fn ) ) {
                    deferred[ handler ](function() {
                        returned = fn.apply( this, arguments );
                        if ( returned && jQuery.isFunction( returned.promise ) ) {
                            returned.promise().then( newDefer.resolve, newDefer.reject, newDefer.notify );
                        } else {
                            newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] );
                        }
                    });
                } else {
                    deferred[ handler ]( newDefer[ action ] );
                }
            });
        }).promise();
    },
    // Get a promise for this deferred
    // If obj is provided, the promise aspect is added to the object
    promise: function( obj ) {                            //如果设置了obj参数对象则为obj对象增加异步队列的方法并返回。如果未设置参数obj,则返回当前Deferred对象的只读副本,
        if ( obj == null ) {                                //如果obj为空
            obj = promise;                                        //则返回promise对象(是上一层作用域链的promise对象)
        } else {
            for ( var key in promise ) {                    //否则遍历promise
                obj[ key ] = promise[ key ];                    //将promise的所有方法、属性保存到obj中
            }
        }
        return obj;                                            //最后返回obj
    }
},

$.Deferred就是对Callbacks回调函数列表的管理而已,产生了一个新的$.Defferred接口,内部添加了一个state表示异步队列的状态。

原文地址:https://www.cnblogs.com/greatdesert/p/11454359.html

时间: 2024-08-05 13:34:16

jQuery源码分析(九) 异步队列模块 Deferred 详解的相关文章

jQuery 源码解析(八) 异步队列模块 Callbacks 回调函数详解

异步队列用于实现异步任务和回调函数的解耦,为ajax模块.队列模块.ready事件提供基础功能,包含三个部分:Query.Callbacks(flags).jQuery.Deferred(funct)和jQuery.when().本节讲解Callbacks,也就是回调函数列表 回调函数用于管理一组回调函数,支持添加.移除.触发.锁定和禁用回调函数,为jQuery.ajax.jQuery.Deferred()和ready()事件提供基础功能,我们也可以基于它编写新的组件. 使用方法:$.Callb

jQuery源码分析系列(38) : 队列操作

Queue队列,如同data数据缓存与Deferred异步模型一样,都是jQuery库的内部实现的基础设施 Queue队列是animate动画依赖的基础设施,整个jQuery中队列仅供给动画使用 Queue队列 队列是一种特殊的线性表,只允许在表的前端(队头)进行删除操作(出队),在表的后端(队尾)进行插入操作(入队).队列的特点是先进先出(FIFO-first in first out),即最先插入的元素最先被删除. 为什么要引入队列? 我们知道代码的执行流有异步与同步之分,例如 var a

jQuery 源码分析(十三) 数据操作模块 DOM属性 详解

jQuery的属性操作模块总共有4个部分,本篇说一下第2个部分:DOM属性部分,用于修改DOM元素的属性的(属性和特性是不一样的,一般将property翻译为属性,attribute翻译为特性) DOM属性的静态方法接口如下: prop(elem, name, value)    ;设置或读取DOM属性,有两种用法,如下 ·$.prop(elem,name,value)      ;传入第三个参数表示设置elem元素的name属性值为value ·$.prop(elem,name,)      

Vue 2.0 深入源码分析(六) 基础篇 computed 属性详解

用法 模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的.在模板中放入太多的逻辑会让模板过重且难以维护,比如: <div id="example">{{ message.split('').reverse().join('') }}</div> <script> var app = new Vue({ el:'#example', data:{message:'hello world'} }) </script> 这样模板不再是简

JDK源码分析(12)之 ConcurrentHashMap 详解

本文将主要讲述 JDK1.8 版本 的 ConcurrentHashMap,其内部结构和很多的哈希优化算法,都是和 JDK1.8 版本的 HashMap是一样的,所以在阅读本文之前,一定要先了解 HashMap,可以参考 HashMap 相关:另外 ConcurrentHashMap 中同样有红黑树,这部分可以先不看不影响整体结构把握,有兴趣的可以查看 红黑树: 一.ConcurrentHashMap 结构概述 1. 整体概述 CHM 的源码有 6k 多行,包含的内容多,精巧,不容易理解:建议在

Vue 2.0 深入源码分析(五) 基础篇 methods属性详解

用法 methods中定义了Vue实例的方法,官网是这样介绍的: 例如:: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <script src="https://cdn.bootcss.com/vue/2.5.16/vue.js"></script> <title>Document&

[转]jQuery源码分析系列

文章转自:jQuery源码分析系列-Aaron 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://github.com/JsAaron/jQuery 正在编写的书 - jQuery架构设计与实现 本人在慕课网的教程(完结) jQuery源码解析(架构与依赖模块) 64课时 jQuery源码解析(DOM与核心模块)64课时 jQuery源码分析目录(完结) jQuery源码分析系列(01) : 整体架构 jQuery源码分析系列(

jQuery源码分析系列(39) : 动画队列

data函数在jQuery中只有短短的300行代码,非常不起点 ,剖析源码的时候你会发现jQuery只要在有需要保存数据的地方无时无刻不依赖这个基础设施 动画会调用队列,队列会调用data数据接口还保存队列里面的的动画数据 所以我们在自习回顾下关于数据缓存 //These may be used throughout the jQuery core codebase //存数据的 //用户使用 data_user = new Data(); //存储对象 //jQuery内部私有 //用来存事件

jQuery源码分析系列(37) : Ajax 总结

综合前面的分析,我们总结如下3大块: jQuery1.5以后,AJAX模块提供了三个新的方法用于管理.扩展AJAX请求 前置过滤器 jQuery. ajaxPrefilter 请求分发器 jQuery. ajaxTransport 类型转换器 ajaxConvert 为了整体性与扩展性考虑,把整个结构通过Deferred实现异步链式模型,Promise对象可以轻易的绑定成功.失败.进行中三种状态的回调函数,然后通过在状态码在来回调不同的函数就行了 出于同源策略考虑,存在跨域问题,所以ajax内部