>웹 프론트엔드 >JS 튜토리얼 >Deferred 비동기 객체의 jQuery 소스 코드 파싱 방법

Deferred 비동기 객체의 jQuery 소스 코드 파싱 방법

一个新手
一个新手원래의
2017-10-26 10:21:171421검색

우리 작업에서는 간단한 DOM 선택기를 제공하고 와 같은 많은 실용적인 메서드를 캡슐화하기 때문에 <code>jQuery를 우리 프로젝트의 기본 라이브러리로 선택할 수 있습니다. $.ajax()를 사용하면 xhrxdr 개체를 작동하지 않고도 코드 논리를 직접 작성할 수 있습니다. 더욱 풍부한 점은 ES6가 기본 지원을 하지 않았던 기간 동안 Promise 개체와 유사한 Deferred 개체를 제공하여 를 지원한다는 것입니다. >done/fail/progress/always 방법과 when 일괄 처리 방법이 프로젝트에 도움이 될 수 있습니다. jQuery选择做自己项目的基础库,因为其提供了简便的DOM选择器以及封装了很多实用的方法,比如$.ajax(),它使得我们不用操作xhrxdr对象,直接书写我们的代码逻辑即可。更为丰富的是它在ES6没有原生支持的那段时间,提供了Deferred对象,类似于Promise对象,支持done/fail/progress/always方法和when批处理方法,这可能在项目上帮助过你。

ES6提供了Promise对象,但由于它是内置C++实现的,所以你也没法看它的设计。不如我们通过jQuery的源码来探究其设计思路,并比较一下两者的区别。本文采用jquey-3.1.2.js版本,其中英文注释为原版,中文注释为我添加。

jQueryajax总体设计

jQuery在内部设置了全局的ajax参数,在每一个ajax请求初始化时,用传递的参数与默认的全局参数进行混合,并构建一个jqXHR对象(提供比原生XHR更为丰富的方法,同时实现其原生方法),通过传递的参数,来判断其是否跨域、传递的参数类型等,设置好相关头部信息。同时其被初始化为一个内置Deferred对象用于异步操作(后面讲到),添加done/fail方法作为回调。同时我们也封装了$.get/$.post方法来快捷调用$.ajax方法。

上面提到的Deferred对象,与ES6的Promise对象类似,用于更为方便的异步操作,多种回调以及更好的书写方式。提供progress/fail/done方法,并分别用该对象的notify/reject/resolve方法触发,可以使用then方法快速设置三个方法,使用always添加都会执行的回调,并且提供when方法支持多个异步操作合并回调。可以追加不同的回调列表,其回调列表是使用内部Callbacks对象,更方便的按照队列的方式来进行执行。

Callbacks回调队列对象,用于构建易于操作的回调函数集合,在操作完成后进行执行。支持四种初始化的方式once/unique/memory/stopOnFalse,分别代表只执行依次、去重、缓存结果、链式调用支持终止。提供fired/locked/disabled状态值,代表是否执行过、上锁、禁用。提供add/remove/empty/fire/lock/disable方法操作回调函数队列。

主要涉及到的概念就是这三个,不再做延伸,三个对象的设计代码行数在1200行左右,断断续续看了我一周 (´ཀ`」 ∠) 。我们从这三个倒序开始入手剖析其设计。

jQuery.Callbacks对象

Callbacks对象,用于管理回调函数的多用途列表。它提供了六个主要方法:

  1. add: 向列表中添加回调函数

  2. remove: 移除列表中的回调函数

  3. empty: 清空列表中的回调函数

  4. fire: 依次执行列表中的回调函数

  5. lock: 对列表上锁,禁止一切操作,清除数据,但保留缓存的环境变量(只在memory参数时有用)

  6. disable: 禁用该回调列表,所有数据清空

在初始化时,支持四个参数,用空格分割:

  1. once: 该回调列表只执行依次

  2. memory: 缓存执行环境,在添加新回调时执行先执行一次

  3. unique: 去重,每一个函数均不同(指的是引用地址)

  4. stopOnFalse: 在调用中,如果前一个函数返回false,中断列表的后续执行

我们来看下其实例使用:

let cl = $.Callbacks(&#39;once memory unique stopOnFalse&#39;);
fn1 = function (data) {
    console.log(data);
};
fn2 = function (data) {
    console.log(&#39;fn2 say:&#39;, data);
    return false;
};
cl.add(fn1);
cl.fire(&#39;Nicholas&#39;);    // Nicholas
// 由于我们使用memory参数,保存了执行环境,在添加新的函数时自动执行一次
cl.add(fn2);    // fn2 say: Nicholas
// 由于我们使用once参数,所以只能执行(fire)一次,此处无任何输出
cl.fire(&#39;Lee&#39;);

// 后面我们假设这里没有传入once参数,每次fire都可以执行

cl.fire(&#39;Lee&#39;);    // Lee    fn2 say: Lee
// 清空列表
cl.empty();
cl.add(fn2, fn1);
// 由于我们设置了stopOnFalse,而fn2返回了false,则后添加的fn1不会执行
cl.fire(&#39;Nicholas&#39;);    // fn2 say: Nicholas
// 上锁cl,禁用其操作,清除数据,但是我们添加了memory参数,它依然会对后续添加的执行一次
cl.lock();
// 无响应
cl.fire();
cl.add(fn2);    // fn2 say: Nicholas
// 禁用cl,禁止一切操作,清除数据
cl.disable();

除了上面所说的主要功能,还提供has/locked/disabled/fireWith/fired等辅助函数。

其所有源码实现及注释为:

jQuery.Callbacks = function( options ) {
    options = typeof options === "string" ?
        // 将字符串中空格分割的子串,转换为值全为true的对象属性
        createOptions( options ) :
        jQuery.extend( {}, options );

    var // Flag to know if list is currently firing
        firing,

        // Last fire value for non-forgettable lists
        memory,

        // Flag to know if list was already fired
        fired,

        // Flag to prevent firing
        locked,

        // Actual callback list
        list = [],

        // Queue of execution data for repeatable lists
        queue = [],

        // Index of currently firing callback (modified by add/remove as needed)
        firingIndex = -1,

        // Fire callbacks
        fire = function() {

            // Enforce single-firing
            locked = locked || options.once;

            // Execute callbacks for all pending executions,
            // respecting firingIndex overrides and runtime changes
            fired = firing = true;
            // 为quene队列中不同的[context, args]执行list回调列表,执行过程中会判断stopOnFalse中间中断
            for ( ; queue.length; firingIndex = -1 ) {
                memory = queue.shift();
                while ( ++firingIndex < list.length ) {

                    // Run callback and check for early termination
                    if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false &&
                        options.stopOnFalse ) {

                        // Jump to end and forget the data so .add doesn&#39;t re-fire
                        firingIndex = list.length;
                        memory = false;
                    }
                }
            }

            // Forget the data if we&#39;re done with it
            if ( !options.memory ) {
                memory = false;
            }

            firing = false;

            // Clean up if we&#39;re done firing for good
            // 如果不再执行了,就将保存回调的list清空,对内存更好
            if ( locked ) {

                // Keep an empty list if we have data for future add calls
                if ( memory ) {
                    list = [];

                // Otherwise, this object is spent
                } else {
                    list = "";
                }
            }
        },

        // Actual Callbacks object
        self = {

            // Add a callback or a collection of callbacks to the list
            add: function() {
                if ( list ) {

                    // If we have memory from a past run, we should fire after adding
                    // 如果我们选择缓存执行环境,会在新添加回调时执行一次保存的环境
                    if ( memory && !firing ) {
                        firingIndex = list.length - 1;
                        queue.push( memory );
                    }

                    ( function add( args ) {
                        jQuery.each( args, function( _, arg ) {
                            // 如果是函数,则判断是否去重,如果为类数组,则递归执行该内部函数
                            if ( jQuery.isFunction( arg ) ) {
                                if ( !options.unique || !self.has( arg ) ) {
                                    list.push( arg );
                                }
                            } else if ( arg && arg.length && jQuery.type( arg ) !== "string" ) {

                                // Inspect recursively
                                add( arg );
                            }
                        } );
                    } )( arguments );

                    if ( memory && !firing ) {
                        fire();
                    }
                }
                return this;
            },

            // Remove a callback from the list
            // 移除所有的相同回调,并同步将firingIndex-1
            remove: function() {
                jQuery.each( arguments, function( _, arg ) {
                    var index;
                    while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
                        list.splice( index, 1 );

                        // Handle firing indexes
                        if ( index <= firingIndex ) {
                            firingIndex--;
                        }
                    }
                } );
                return this;
            },

            // Check if a given callback is in the list.
            // If no argument is given, return whether or not list has callbacks attached.
            // 检查是否存在该函数,如果不传递参数,则返回是否有回调函数
            has: function( fn ) {
                return fn ?
                    jQuery.inArray( fn, list ) > -1 :
                    list.length > 0;
            },

            // Remove all callbacks from the list
            empty: function() {
                if ( list ) {
                    list = [];
                }
                return this;
            },

            // Disable .fire and .add
            // Abort any current/pending executions
            // Clear all callbacks and values
            // 置locked为[],即!![] === true,同时将队列和列表都清空,即禁用了该回调集合
            disable: function() {
                locked = queue = [];
                list = memory = "";
                return this;
            },
            disabled: function() {
                return !list;
            },

            // Disable .fire
            // Also disable .add unless we have memory (since it would have no effect)
            // Abort any pending executions
            // 不允许执行,但如果有缓存,则我们允许添加后在缓存的环境下执行新添加的回调
            lock: function() {
                locked = queue = [];
                if ( !memory && !firing ) {
                    list = memory = "";
                }
                return this;
            },
            locked: function() {
                return !!locked;
            },

            // Call all callbacks with the given context and arguments
            // 为fire附带了一个上下文来调用fire函数,
            fireWith: function( context, args ) {
                if ( !locked ) {
                    args = args || [];
                    args = [ context, args.slice ? args.slice() : args ];
                    queue.push( args );
                    if ( !firing ) {
                        fire();
                    }
                }
                return this;
            },

            // Call all the callbacks with the given arguments
            fire: function() {
                self.fireWith( this, arguments );
                return this;
            },

            // To know if the callbacks have already been called at least once
            fired: function() {
                return !!fired;
            }
        };

    return self;
};

jQuery.Deferred对象

jQuery.Deferred对象是一个工厂函数,返回一个用于异步或同步调用的deferred对象,支持链式调用、回调函数队列,并且能针对返回的状态不同执行不同的回调。它类似于ES6提供的Promise对象,提供9个主要的方法:

  1. done

    ES6Promise 개체를 제공하지만 내장된 C++로 구현되므로 디자인을 볼 수 없습니다. jQuery의 소스코드를 통해 디자인 아이디어를 살펴보고 둘의 차이점을 비교해 보면 어떨까요? 이 글은 jquey-3.1.2.js 버전을 사용하고 있으며, 영문 댓글은 원본이고 중국어 댓글은 제가 추가한 것입니다. 🎜

    jQueryajax 전체 디자인

    🎜jQuery는 전역 ajax 매개변수를 내부적으로 설정합니다. 각 ajax 요청을 초기화할 때 전달된 매개변수를 기본 전역 매개변수와 혼합하고 jqXHR 객체를 빌드합니다(네이티브 XHR자체 메소드를 구현하는 동안 더 풍부한 메소드) 전달된 매개변수를 통해 크로스 도메인인지 여부, 전달된 매개변수 유형 등을 확인하고 관련 헤더 정보를 설정합니다. 동시에 비동기 작업을 위한 내장 <code>Deferred 개체로 초기화되고(나중에 설명) done/fail 메서드가 콜백으로 추가됩니다. 동시에 $.ajax 메서드를 빠르게 호출하기 위해 $.get/$.post 메서드도 캡슐화했습니다. 🎜🎜위에 언급된 Deferred 개체는 ES6 Promise 개체와 유사하며 보다 편리한 비동기 작업, 다중 콜백 및 더 나은 작성 방법에 사용됩니다. progress/fail/done 메서드를 제공하고 개체의 notify/reject/resolve 메서드로 각각 트리거합니다. then을 사용할 수 있습니다. 세 가지 메서드를 신속하게 설정하려면 always를 사용하여 실행할 콜백을 추가하고 when 메서드를 제공하여 콜백을 병합하는 여러 비동기 작업을 지원하세요. 다양한 콜백 목록을 추가할 수 있습니다. 콜백 목록은 대기열 방식으로 실행하는 데 더 편리한 내부 콜백 개체를 사용합니다. 🎜🎜콜백 콜백 대기열 개체는 작업이 완료된 후 실행될 작동하기 쉬운 콜백 함수 모음을 만드는 데 사용됩니다. 네 가지 초기화 방법 once/unique/memory/stopOnFalse를 지원합니다. 이는 각각 순차 실행 전용, 중복 제거, 캐시된 결과 및 체인 호출 지원 종료를 나타냅니다. 실행, 잠금, 비활성화 여부를 나타내는 fired/locked/disabled 상태 값을 제공합니다. 콜백 함수 대기열을 작동하기 위한 add/remove/empty/fire/lock/disable 메서드를 제공합니다. 🎜🎜포함된 주요 컨셉은 이 3개이며, 추가 확장은 없을 예정입니다. 3개의 개체에 대한 디자인 코드의 줄 수는 약 1,200줄입니다. ('ཀ`' ∠ ). 이 세 가지 역순으로 시작하여 디자인을 분석해 보겠습니다. 🎜

    jQuery.Callbacks 개체

    🎜콜백 개체는 콜백 함수를 관리하는 데 사용되는 다목적 목록입니다. 6가지 주요 메소드를 제공합니다: 🎜
    1. 🎜add: 목록에 콜백 함수 추가 🎜
    2. 🎜 제거
    : 목록에서 콜백 함수 제거🎜
  2. 🎜비어 있음: 목록에서 콜백 함수 지우기🎜
  3. 🎜 fire: 목록의 콜백 함수를 순서대로 실행🎜
  4. 🎜lock: 목록을 잠그고 모든 작업을 금지하며 데이터를 지우지만 캐시된 환경 변수는 유지합니다. (memory 매개변수를 사용할 때만 유용합니다.) 🎜
  5. 🎜disable: 콜백 목록을 비활성화하고 모든 데이터를 삭제합니다🎜
  6. ol> 🎜초기화 중에는 공백으로 구분된 4개의 매개변수가 지원됩니다. 🎜
    1. 🎜한 번: 콜백 목록은 순서대로만 실행됩니다🎜
    2. 🎜memory: 캐시 실행 환경, 새 콜백 추가 시 한 번 실행 🎜
    3. 🎜unique: 중복 제거, 각 함수가 모두 다릅니다(참조 주소 참조) 🎜
    4. 🎜stopOnFalse: 호출 도중 이전 함수가 false를 반환하면 후속 목록 실행이 중단됩니다 🎜
    🎜실제 사용법을 살펴보겠습니다. 🎜
    // 我们封装Deferred产生一个promise对象,其不能被外部手动解析,只能内部确定最终状态
    asynPromise = function () {
        let d = $.Deferred();
        (function timer() {
            setTimeout(function () {
                // 产生随机数,代替解析结果,来确定本次的状态
                let num = Math.random();
                if (num > 0.9) {
                    d.resolve();    // 解析成功
                } else if (num < 0.1) {
                    d.reject();    // 解析失败
                } else {
                    d.notify();    // 解析过程中
                }
                setTimeout(timer, 1000);    // 持续不断的解析数据
            }, 1000);
        })();
        // 如果不返回promise对象,则可以被外部手动调整解析状态
        return d.promise();
    };
    
    // then方法的三个参数分别代表完成、失败、过程中的回调函数
    asynPromise().then(function () {
        console.log(&#39;resolve success&#39;);
    }, function () {
        console.log(&#39;reject fail&#39;);
    }, function () {
        console.log(&#39;notify progress&#39;);
    });
    
    // 本地执行结果(每个人的不一样,随机分布,但最后一个一定是success或fail)
    notify progress
    notify progress
    notify progress
    notify progress
    notify progress
    reject fail    // 后面不会再有输出,因为一旦解析状态为success或fail,则不会再改变
    🎜위에서 언급한 주요 기능 외에도 has/locked/와 같은 보조 기능도 제공합니다. 비활성화/fireWith/해고 기능. 🎜🎜모든 소스 코드 구현 및 설명은 다음과 같습니다. 🎜
    Deferred: function( func ) {
            var tuples = [
                    // action, add listener, callbacks,
                    // ... .then handlers, argument index, [final state]
                    // 用于后面进行第一个参数绑定调用第二个参数,第三个和第四个参数分别是其不同的回调函数队列
                    [ "notify", "progress", jQuery.Callbacks( "memory" ),
                        jQuery.Callbacks( "memory" ), 2 ],
                    [ "resolve", "done", jQuery.Callbacks( "once memory" ),
                        jQuery.Callbacks( "once memory" ), 0, "resolved" ],
                    [ "reject", "fail", jQuery.Callbacks( "once memory" ),
                        jQuery.Callbacks( "once memory" ), 1, "rejected" ]
                ],
                state = "pending",
                promise = {
                    state: function() {
                        return state;
                    },
                    // 同时添加done和fail句柄
                    always: function() {
                        deferred.done( arguments ).fail( arguments );
                        return this;
                    },
                    "catch": function( fn ) {
                        return promise.then( null, fn );
                    },
                    then: function( onFulfilled, onRejected, onProgress ) {
                        var maxDepth = 0;
                        function resolve( depth, deferred, handler, special ) {
                            return function() {
                                var that = this,
                                    args = arguments,
                                    mightThrow = function() {
                                        var returned, then;
    
                                        // Support: Promises/A+ section 2.3.3.3.3
                                        // https://promisesaplus.com/#point-59
                                        // Ignore double-resolution attempts
                                        if ( depth < maxDepth ) {
                                            return;
                                        }
    
                                        returned = handler.apply( that, args );
    
                                        // Support: Promises/A+ section 2.3.1
                                        // https://promisesaplus.com/#point-48
                                        if ( returned === deferred.promise() ) {
                                            throw new TypeError( "Thenable self-resolution" );
                                        }
    
                                        // Support: Promises/A+ sections 2.3.3.1, 3.5
                                        // https://promisesaplus.com/#point-54
                                        // https://promisesaplus.com/#point-75
                                        // Retrieve `then` only once
                                        then = returned &&
    
                                            // Support: Promises/A+ section 2.3.4
                                            // https://promisesaplus.com/#point-64
                                            // Only check objects and functions for thenability
                                            ( typeof returned === "object" ||
                                                typeof returned === "function" ) &&
                                            returned.then;
    
                                        // Handle a returned thenable
                                        if ( jQuery.isFunction( then ) ) {
    
                                            // Special processors (notify) just wait for resolution
                                            if ( special ) {
                                                then.call(
                                                    returned,
                                                    resolve( maxDepth, deferred, Identity, special ),
                                                    resolve( maxDepth, deferred, Thrower, special )
                                                );
    
                                            // Normal processors (resolve) also hook into progress
                                            } else {
    
                                                // ...and disregard older resolution values
                                                maxDepth++;
    
                                                then.call(
                                                    returned,
                                                    resolve( maxDepth, deferred, Identity, special ),
                                                    resolve( maxDepth, deferred, Thrower, special ),
                                                    resolve( maxDepth, deferred, Identity,
                                                        deferred.notifyWith )
                                                );
                                            }
    
                                        // Handle all other returned values
                                        } else {
    
                                            // Only substitute handlers pass on context
                                            // and multiple values (non-spec behavior)
                                            if ( handler !== Identity ) {
                                                that = undefined;
                                                args = [ returned ];
                                            }
    
                                            // Process the value(s)
                                            // Default process is resolve
                                            ( special || deferred.resolveWith )( that, args );
                                        }
                                    },
    
                                    // Only normal processors (resolve) catch and reject exceptions
                                    // 只有普通的process能处理异常,其余的要进行捕获,这里不是特别明白,应该是因为没有改最终的状态吧
                                    process = special ?
                                        mightThrow :
                                        function() {
                                            try {
                                                mightThrow();
                                            } catch ( e ) {
    
                                                if ( jQuery.Deferred.exceptionHook ) {
                                                    jQuery.Deferred.exceptionHook( e,
                                                        process.stackTrace );
                                                }
    
                                                // Support: Promises/A+ section 2.3.3.3.4.1
                                                // https://promisesaplus.com/#point-61
                                                // Ignore post-resolution exceptions
                                                if ( depth + 1 >= maxDepth ) {
    
                                                    // Only substitute handlers pass on context
                                                    // and multiple values (non-spec behavior)
                                                    if ( handler !== Thrower ) {
                                                        that = undefined;
                                                        args = [ e ];
                                                    }
    
                                                    deferred.rejectWith( that, args );
                                                }
                                            }
                                        };
    
                                // 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 {
    
                                    // Call an optional hook to record the stack, in case of exception
                                    // since it&#39;s otherwise lost when execution goes async
                                    if ( jQuery.Deferred.getStackHook ) {
                                        process.stackTrace = jQuery.Deferred.getStackHook();
                                    }
                                    window.setTimeout( process );
                                }
                            };
                        }
    
                        return jQuery.Deferred( function( newDefer ) {
    
                            // progress_handlers.add( ... )
                            tuples[ 0 ][ 3 ].add(
                                resolve(
                                    0,
                                    newDefer,
                                    jQuery.isFunction( onProgress ) ?
                                        onProgress :
                                        Identity,
                                    newDefer.notifyWith
                                )
                            );
    
                            // fulfilled_handlers.add( ... )
                            tuples[ 1 ][ 3 ].add(
                                resolve(
                                    0,
                                    newDefer,
                                    jQuery.isFunction( onFulfilled ) ?
                                        onFulfilled :
                                        Identity
                                )
                            );
    
                            // rejected_handlers.add( ... )
                            tuples[ 2 ][ 3 ].add(
                                resolve(
                                    0,
                                    newDefer,
                                    jQuery.isFunction( onRejected ) ?
                                        onRejected :
                                        Thrower
                                )
                            );
                        } ).promise();
                    },
    
                    // Get a promise for this deferred
                    // If obj is provided, the promise aspect is added to the object
                    // 通过该promise对象返回一个新的扩展promise对象或自身
                    promise: function( obj ) {
                        return obj != null ? jQuery.extend( obj, promise ) : promise;
                    }
                },
                deferred = {};
    
            // Add list-specific methods
            // 给promise添加done/fail/progress事件,并添加互相的影响关系,并为deferred对象添加3个事件函数notify/resolve/reject
            jQuery.each( tuples, function( i, tuple ) {
                var list = tuple[ 2 ],
                    stateString = tuple[ 5 ];
    
                // promise.progress = list.add
                // promise.done = list.add
                // promise.fail = list.add
                promise[ tuple[ 1 ] ] = list.add;
    
                // Handle state
                // 只有done和fail有resolved和rejected状态字段,给两个事件添加回调,禁止再次done或者fail,锁住progress不允许执行回调
                if ( stateString ) {
                    list.add(
                        function() {
    
                            // state = "resolved" (i.e., fulfilled)
                            // state = "rejected"
                            state = stateString;
                        },
    
                        // rejected_callbacks.disable
                        // fulfilled_callbacks.disable
                        tuples[ 3 - i ][ 2 ].disable,
    
                        // progress_callbacks.lock
                        tuples[ 0 ][ 2 ].lock
                    );
                }
    
                // progress_handlers.fire
                // fulfilled_handlers.fire
                // rejected_handlers.fire
                // 执行第二个回调列表
                list.add( tuple[ 3 ].fire );
    
                // deferred.notify = function() { deferred.notifyWith(...) }
                // deferred.resolve = function() { deferred.resolveWith(...) }
                // deferred.reject = function() { deferred.rejectWith(...) }
                // 绑定notify/resolve/reject的事件,实际执行的函数体为加入上下文的With函数
                deferred[ tuple[ 0 ] ] = function() {
                    deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments );
                    return this;
                };
    
                // deferred.notifyWith = list.fireWith
                // deferred.resolveWith = list.fireWith
                // deferred.rejectWith = list.fireWith
                deferred[ tuple[ 0 ] + "With" ] = list.fireWith;
            } );
    
            // Make the deferred a promise
            // 将deferred扩展为一个promise对象
            promise.promise( deferred );
    
            // Call given func if any
            // 在创建前执行传入的回调函数进行修改
            if ( func ) {
                func.call( deferred, deferred );
            }
    
            // All done!
            return deferred;
        },

    jQuery.Deferred 객체

    🎜jQuery.Deferred 객체는 비동기식 또는 동기식으로 호출되는 사용자 지연 개체는 체인 호출과 콜백 함수 대기열을 지원하고 다양한 반환 상태에 따라 다양한 콜백을 실행할 수 있습니다. ES6에서 제공하는 Promise 객체와 유사하며 9가지 주요 메소드를 제공합니다: 🎜
    1. 🎜 done: 작업이 성공적으로 응답할 때의 콜백 함수(동기식 또는 비동기식, 아래와 동일) 🎜
    2. fail: 操作失败响应时的回调函数

    3. progress: 操作处理过程中的回调函数

    4. resolve: 通过该方法解析该操作为成功状态,调用done

    5. reject: 通过该方法解析该操作为失败状态,调用fail

    6. notify: 通过该方法解析该操作为执行过程中,调用progress

    7. then: 设置回调的简写,接收三个参数,分别是done/fail/progress

    8. always: 设置必须执行的回调,无论是done还是fail

    9. promise: 返回一个受限制的Deferred对象,不允许外部直接改变完成状态

    它的实现思想是创建一个对象,包含不同状态下回调函数的队列,并在状态为失败或成功后不允许再次改变。通过返回的Deferred对象进行手动调用resolve/reject/notify方法来控制流程。

    看一个实例(纯属胡扯,不要当真)。我们需要从间谍卫星返回的数据用不同的算法来进行解析,如果解析结果信号强度大于90%,则证明该数据有效,可以被解析;如果强度小于10%,则证明只是宇宙噪音;否则,证明数据可能有效,换一种算法解析:

    // 我们封装Deferred产生一个promise对象,其不能被外部手动解析,只能内部确定最终状态
    asynPromise = function () {
        let d = $.Deferred();
        (function timer() {
            setTimeout(function () {
                // 产生随机数,代替解析结果,来确定本次的状态
                let num = Math.random();
                if (num > 0.9) {
                    d.resolve();    // 解析成功
                } else if (num < 0.1) {
                    d.reject();    // 解析失败
                } else {
                    d.notify();    // 解析过程中
                }
                setTimeout(timer, 1000);    // 持续不断的解析数据
            }, 1000);
        })();
        // 如果不返回promise对象,则可以被外部手动调整解析状态
        return d.promise();
    };
    
    // then方法的三个参数分别代表完成、失败、过程中的回调函数
    asynPromise().then(function () {
        console.log(&#39;resolve success&#39;);
    }, function () {
        console.log(&#39;reject fail&#39;);
    }, function () {
        console.log(&#39;notify progress&#39;);
    });
    
    // 本地执行结果(每个人的不一样,随机分布,但最后一个一定是success或fail)
    notify progress
    notify progress
    notify progress
    notify progress
    notify progress
    reject fail    // 后面不会再有输出,因为一旦解析状态为success或fail,则不会再改变

    除了上面的主要功能,还提供了notifyWith/resolveWith/rejectWith/state辅助方法。

    其所有的源码实现和注释为:

    Deferred: function( func ) {
            var tuples = [
                    // action, add listener, callbacks,
                    // ... .then handlers, argument index, [final state]
                    // 用于后面进行第一个参数绑定调用第二个参数,第三个和第四个参数分别是其不同的回调函数队列
                    [ "notify", "progress", jQuery.Callbacks( "memory" ),
                        jQuery.Callbacks( "memory" ), 2 ],
                    [ "resolve", "done", jQuery.Callbacks( "once memory" ),
                        jQuery.Callbacks( "once memory" ), 0, "resolved" ],
                    [ "reject", "fail", jQuery.Callbacks( "once memory" ),
                        jQuery.Callbacks( "once memory" ), 1, "rejected" ]
                ],
                state = "pending",
                promise = {
                    state: function() {
                        return state;
                    },
                    // 同时添加done和fail句柄
                    always: function() {
                        deferred.done( arguments ).fail( arguments );
                        return this;
                    },
                    "catch": function( fn ) {
                        return promise.then( null, fn );
                    },
                    then: function( onFulfilled, onRejected, onProgress ) {
                        var maxDepth = 0;
                        function resolve( depth, deferred, handler, special ) {
                            return function() {
                                var that = this,
                                    args = arguments,
                                    mightThrow = function() {
                                        var returned, then;
    
                                        // Support: Promises/A+ section 2.3.3.3.3
                                        // https://promisesaplus.com/#point-59
                                        // Ignore double-resolution attempts
                                        if ( depth < maxDepth ) {
                                            return;
                                        }
    
                                        returned = handler.apply( that, args );
    
                                        // Support: Promises/A+ section 2.3.1
                                        // https://promisesaplus.com/#point-48
                                        if ( returned === deferred.promise() ) {
                                            throw new TypeError( "Thenable self-resolution" );
                                        }
    
                                        // Support: Promises/A+ sections 2.3.3.1, 3.5
                                        // https://promisesaplus.com/#point-54
                                        // https://promisesaplus.com/#point-75
                                        // Retrieve `then` only once
                                        then = returned &&
    
                                            // Support: Promises/A+ section 2.3.4
                                            // https://promisesaplus.com/#point-64
                                            // Only check objects and functions for thenability
                                            ( typeof returned === "object" ||
                                                typeof returned === "function" ) &&
                                            returned.then;
    
                                        // Handle a returned thenable
                                        if ( jQuery.isFunction( then ) ) {
    
                                            // Special processors (notify) just wait for resolution
                                            if ( special ) {
                                                then.call(
                                                    returned,
                                                    resolve( maxDepth, deferred, Identity, special ),
                                                    resolve( maxDepth, deferred, Thrower, special )
                                                );
    
                                            // Normal processors (resolve) also hook into progress
                                            } else {
    
                                                // ...and disregard older resolution values
                                                maxDepth++;
    
                                                then.call(
                                                    returned,
                                                    resolve( maxDepth, deferred, Identity, special ),
                                                    resolve( maxDepth, deferred, Thrower, special ),
                                                    resolve( maxDepth, deferred, Identity,
                                                        deferred.notifyWith )
                                                );
                                            }
    
                                        // Handle all other returned values
                                        } else {
    
                                            // Only substitute handlers pass on context
                                            // and multiple values (non-spec behavior)
                                            if ( handler !== Identity ) {
                                                that = undefined;
                                                args = [ returned ];
                                            }
    
                                            // Process the value(s)
                                            // Default process is resolve
                                            ( special || deferred.resolveWith )( that, args );
                                        }
                                    },
    
                                    // Only normal processors (resolve) catch and reject exceptions
                                    // 只有普通的process能处理异常,其余的要进行捕获,这里不是特别明白,应该是因为没有改最终的状态吧
                                    process = special ?
                                        mightThrow :
                                        function() {
                                            try {
                                                mightThrow();
                                            } catch ( e ) {
    
                                                if ( jQuery.Deferred.exceptionHook ) {
                                                    jQuery.Deferred.exceptionHook( e,
                                                        process.stackTrace );
                                                }
    
                                                // Support: Promises/A+ section 2.3.3.3.4.1
                                                // https://promisesaplus.com/#point-61
                                                // Ignore post-resolution exceptions
                                                if ( depth + 1 >= maxDepth ) {
    
                                                    // Only substitute handlers pass on context
                                                    // and multiple values (non-spec behavior)
                                                    if ( handler !== Thrower ) {
                                                        that = undefined;
                                                        args = [ e ];
                                                    }
    
                                                    deferred.rejectWith( that, args );
                                                }
                                            }
                                        };
    
                                // 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 {
    
                                    // Call an optional hook to record the stack, in case of exception
                                    // since it&#39;s otherwise lost when execution goes async
                                    if ( jQuery.Deferred.getStackHook ) {
                                        process.stackTrace = jQuery.Deferred.getStackHook();
                                    }
                                    window.setTimeout( process );
                                }
                            };
                        }
    
                        return jQuery.Deferred( function( newDefer ) {
    
                            // progress_handlers.add( ... )
                            tuples[ 0 ][ 3 ].add(
                                resolve(
                                    0,
                                    newDefer,
                                    jQuery.isFunction( onProgress ) ?
                                        onProgress :
                                        Identity,
                                    newDefer.notifyWith
                                )
                            );
    
                            // fulfilled_handlers.add( ... )
                            tuples[ 1 ][ 3 ].add(
                                resolve(
                                    0,
                                    newDefer,
                                    jQuery.isFunction( onFulfilled ) ?
                                        onFulfilled :
                                        Identity
                                )
                            );
    
                            // rejected_handlers.add( ... )
                            tuples[ 2 ][ 3 ].add(
                                resolve(
                                    0,
                                    newDefer,
                                    jQuery.isFunction( onRejected ) ?
                                        onRejected :
                                        Thrower
                                )
                            );
                        } ).promise();
                    },
    
                    // Get a promise for this deferred
                    // If obj is provided, the promise aspect is added to the object
                    // 通过该promise对象返回一个新的扩展promise对象或自身
                    promise: function( obj ) {
                        return obj != null ? jQuery.extend( obj, promise ) : promise;
                    }
                },
                deferred = {};
    
            // Add list-specific methods
            // 给promise添加done/fail/progress事件,并添加互相的影响关系,并为deferred对象添加3个事件函数notify/resolve/reject
            jQuery.each( tuples, function( i, tuple ) {
                var list = tuple[ 2 ],
                    stateString = tuple[ 5 ];
    
                // promise.progress = list.add
                // promise.done = list.add
                // promise.fail = list.add
                promise[ tuple[ 1 ] ] = list.add;
    
                // Handle state
                // 只有done和fail有resolved和rejected状态字段,给两个事件添加回调,禁止再次done或者fail,锁住progress不允许执行回调
                if ( stateString ) {
                    list.add(
                        function() {
    
                            // state = "resolved" (i.e., fulfilled)
                            // state = "rejected"
                            state = stateString;
                        },
    
                        // rejected_callbacks.disable
                        // fulfilled_callbacks.disable
                        tuples[ 3 - i ][ 2 ].disable,
    
                        // progress_callbacks.lock
                        tuples[ 0 ][ 2 ].lock
                    );
                }
    
                // progress_handlers.fire
                // fulfilled_handlers.fire
                // rejected_handlers.fire
                // 执行第二个回调列表
                list.add( tuple[ 3 ].fire );
    
                // deferred.notify = function() { deferred.notifyWith(...) }
                // deferred.resolve = function() { deferred.resolveWith(...) }
                // deferred.reject = function() { deferred.rejectWith(...) }
                // 绑定notify/resolve/reject的事件,实际执行的函数体为加入上下文的With函数
                deferred[ tuple[ 0 ] ] = function() {
                    deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments );
                    return this;
                };
    
                // deferred.notifyWith = list.fireWith
                // deferred.resolveWith = list.fireWith
                // deferred.rejectWith = list.fireWith
                deferred[ tuple[ 0 ] + "With" ] = list.fireWith;
            } );
    
            // Make the deferred a promise
            // 将deferred扩展为一个promise对象
            promise.promise( deferred );
    
            // Call given func if any
            // 在创建前执行传入的回调函数进行修改
            if ( func ) {
                func.call( deferred, deferred );
            }
    
            // All done!
            return deferred;
        },

    jQuery.when方法

    $.when()提供一种方法执行一个或多个函数的回调函数。如果传入一个延迟对象,则返回该对象的Promise对象,可以继续绑定其余回调,在执行结束状态之后也同时调用其when回调函数。如果传入多个延迟对象,则返回一个新的master延迟对象,跟踪所有的聚集状态,如果都成功解析完成,才调用其when回调函数;如果有一个失败,则全部失败,执行错误回调。

    其使用方法:

    $.when($.ajax("/page1.php"), $.ajax("/page2.php"))
      .then(myFunc, myFailure);

    其所有源码实现和注释为(能力有限,有些地方实在不能准确理解执行流程):

    // 给when传递的对象绑定master.resolve和master.reject,用于聚集多异步对象的状态
    function adoptValue( value, resolve, reject, noValue ) {
        var method;
        try {
            // Check for promise aspect first to privilege synchronous behavior
            // 如果when传入的参数promise方法可用,则封装promise并添加done和fail方法调用resolve和reject
            if ( value && jQuery.isFunction( ( method = value.promise ) ) ) {
                method.call( value ).done( resolve ).fail( reject );
    
            // Other thenables
            // 否则,就判断传入参数的then方法是否可用,如果可用就传入resolve和reject方法
            } else if ( value && jQuery.isFunction( ( method = value.then ) ) ) {
                method.call( value, resolve, reject );
    
            // Other non-thenables
            // 如果均不可用,则为非异步对象,直接resolve解析原值
            } else {
    
                // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer:
                // * false: [ value ].slice( 0 ) => resolve( value )
                // * true: [ value ].slice( 1 ) => resolve()
                resolve.apply( undefined, [ value ].slice( noValue ) );
            }
    
        // For Promises/A+, convert exceptions into rejections
        // Since jQuery.when doesn&#39;t unwrap thenables, we can skip the extra checks appearing in
        // Deferred#then to conditionally suppress rejection.
        } catch ( value ) {
    
            // Support: Android 4.0 only
            // Strict mode functions invoked without .call/.apply get global-object context
            // 一个安卓4.0的bug,这里不做阐释
            reject.apply( undefined, [ value ] );
        }
    }
    
    // Deferred helper
        when: function( singleValue ) {
            var
                // count of uncompleted subordinates
                remaining = arguments.length,
    
                // count of unprocessed arguments
                i = remaining,
    
                // subordinate fulfillment data
                resolveContexts = Array( i ),
                resolveValues = slice.call( arguments ),
    
                // the master Deferred
                master = jQuery.Deferred(),
    
                // subordinate callback factory
                // 将每一个响应的环境和值都保存到列表里,在全部完成后统一传给主Promise用于执行
                updateFunc = function( i ) {
                    return function( value ) {
                        resolveContexts[ i ] = this;
                        resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
                        if ( !( --remaining ) ) {
                            master.resolveWith( resolveContexts, resolveValues );
                        }
                    };
                };
    
            // Single- and empty arguments are adopted like Promise.resolve
            // 如果只有一个参数,则直接将其作为master的回调
            if ( remaining <= 1 ) {
                adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject,
                    !remaining );
    
                // Use .then() to unwrap secondary thenables (cf. gh-3000)
                if ( master.state() === "pending" ||
                    jQuery.isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) {
    
                    return master.then();
                }
            }
    
            // Multiple arguments are aggregated like Promise.all array elements
            // 多参数时,进行所有参数的解析状态聚合到master上
            while ( i-- ) {
                adoptValue( resolveValues[ i ], updateFunc( i ), master.reject );
            }
    
            return master.promise();
        }

    后续

    本来想把jQuery.DeferredjQuery.ajax以及ES6Promise对象给统一讲一下,结果发现牵涉的东西太多,每一个都可以单独写一篇文章,怕大家说太长不看,这里先写第一部分jQuery.Deferred吧,后续再补充另外两篇。

    jQuery的文档很容易,使用也很方便,但其实真正想要讲好很复杂,更不要说写篇源码分析文章了。真的是努力理解设计者的思路,争取每行都能理解边界条件,但踩坑太少,应用场景太少,确实有很大的疏漏,希望大家能够理解,不要偏听一面之词。


위 내용은 Deferred 비동기 객체의 jQuery 소스 코드 파싱 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.