Dans notre travail, nous pouvons choisir jQuery
comme bibliothèque de base de nos propres projets, car elle fournit un simple sélecteur DOM
et encapsule de nombreuses méthodes pratiques, telles que $.ajax()
, ce qui nous évite d'avoir à utiliser Pour les objets xhr
et xdr
, nous pouvons directement écrire notre logique de code. Ce qui est encore plus riche, c'est que pendant la période où ES6
n'avait pas de support natif, il fournissait un objet Deferred
, similaire à l'objet Promise
, supportant la méthode done/fail/progress/always
et la méthode batch when
, qui peuvent être sur le projet Vous a aidé.
ES6
fournit l'objet Promise
, mais comme il est implémenté par le C++
intégré, vous ne pouvez pas voir sa conception. Que diriez-vous d'explorer ses idées de conception à travers le code source de jQuery
et de comparer les différences entre les deux. Cet article adopte la version jquey-3.1.2.js
, dans laquelle les annotations anglaises sont la version originale et les annotations chinoises ont été ajoutées par moi. La conception globale
jQuery
de ajax
jQuery
définit les paramètres globaux ajax
en interne Lors de l'initialisation de chaque ajax
requête, les paramètres transmis sont utilisés avec le global par défaut. les paramètres sont mélangés et un objet jqXHR
est construit (qui fournit des méthodes plus riches que le XHR
natif et implémente ses méthodes natives), et détermine s'il est inter-domaine et le type de paramètres transmis par les paramètres transmis. définir les informations d'en-tête pertinentes. En même temps, il est initialisé en tant qu'objet Deferred
intégré pour les opérations asynchrones (discuté plus tard), et la méthode done/fail
est ajoutée en tant que rappel. Dans le même temps, nous encapsulons également la méthode $.get/$.post
pour appeler rapidement la méthode $.ajax
.
L'objet Deferred
mentionné ci-dessus est similaire à l'objet Promise
d'ES6 et est utilisé pour des opérations asynchrones plus pratiques, des rappels multiples et de meilleures méthodes d'écriture. Fournissez les méthodes progress/fail/done
et déclenchez-les respectivement avec la méthode notify/reject/resolve
de l'objet. Vous pouvez utiliser la méthode then
pour configurer rapidement trois méthodes, utiliser always
pour ajouter des rappels qui seront tous exécutés et fournir les méthodes when
. Callbacks
méthode pour prendre en charge plusieurs rappels de fusion d'opérations asynchrones. Différentes listes de rappel peuvent être ajoutées. La liste de rappel utilise l'objet interne
Callbacks
once/unique/memory/stopOnFalse
Objet de file d'attente de rappel, utilisé pour créer une collection de fonctions de rappel faciles à utiliser et à exécuter une fois l'opération terminée. Prend en charge quatre méthodes d'initialisation fired/locked/disabled
, qui représentent respectivement l'exécution séquentielle, la déduplication, les résultats mis en cache et la fin de la prise en charge des appels en chaîne. Fournissez la valeur d'état add/remove/empty/fire/lock/disable
, indiquant si elle a été exécutée, verrouillée ou désactivée. Fournit la méthode
Les principaux concepts impliqués sont ces trois-là et ne seront pas étendus. Le nombre de lignes de code de conception pour les trois objets est d'environ 1200 lignes. Je le lis par intermittence depuis une semaine(´ཀ. `‖ ∠)
. Analysons sa conception en commençant par ces trois éléments dans l'ordre inverse.jQuery.Callbacks
Callbacks
add
remove
empty
fire
lock
memory
: Verrouiller la liste, interdire toutes les opérations, effacer les données, mais conserver les variables d'environnement en cache (utile uniquement lorsque le paramètre
disable
once
memory
unique
stopOnFalse
false
: Dans l'appel, si la fonction précédente renvoie
let cl = $.Callbacks('once memory unique stopOnFalse'); fn1 = function (data) { console.log(data); }; fn2 = function (data) { console.log('fn2 say:', data); return false; }; cl.add(fn1); cl.fire('Nicholas'); // Nicholas // 由于我们使用memory参数,保存了执行环境,在添加新的函数时自动执行一次 cl.add(fn2); // fn2 say: Nicholas // 由于我们使用once参数,所以只能执行(fire)一次,此处无任何输出 cl.fire('Lee'); // 后面我们假设这里没有传入once参数,每次fire都可以执行 cl.fire('Lee'); // Lee fn2 say: Lee // 清空列表 cl.empty(); cl.add(fn2, fn1); // 由于我们设置了stopOnFalse,而fn2返回了false,则后添加的fn1不会执行 cl.fire('Nicholas'); // fn2 say: Nicholas // 上锁cl,禁用其操作,清除数据,但是我们添加了memory参数,它依然会对后续添加的执行一次 cl.lock(); // 无响应 cl.fire(); cl.add(fn2); // fn2 say: Nicholas // 禁用cl,禁止一切操作,清除数据 cl.disable();
has/locked/disabled/fireWith/fired
En plus des fonctions principales mentionnées ci-dessus, il fournit
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't re-fire firingIndex = list.length; memory = false; } } } // Forget the data if we're done with it if ( !options.memory ) { memory = false; } firing = false; // Clean up if we'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
L'objet est une fonction d'usine qui renvoie une fonction pour les tâches asynchrones ou appels synchrones L'objet ES6
prend en charge les appels en chaîne et les files d'attente de fonctions de rappel, et peut exécuter différents rappels en fonction de l'état renvoyé. Il est similaire à l'objet Promise
fourni par
done
fail
: 操作失败响应时的回调函数
progress
: 操作处理过程中的回调函数
resolve
: 通过该方法解析该操作为成功状态,调用done
reject
: 通过该方法解析该操作为失败状态,调用fail
notify
: 通过该方法解析该操作为执行过程中,调用progress
then
: 设置回调的简写,接收三个参数,分别是done/fail/progress
always
: 设置必须执行的回调,无论是done还是fail
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('resolve success'); }, function () { console.log('reject fail'); }, function () { console.log('notify progress'); }); // 本地执行结果(每个人的不一样,随机分布,但最后一个一定是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'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'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.Deferred
和jQuery.ajax
以及ES6
的Promise
对象给统一讲一下,结果发现牵涉的东西太多,每一个都可以单独写一篇文章,怕大家说太长不看,这里先写第一部分jQuery.Deferred
吧,后续再补充另外两篇。
看jQuery
的文档很容易,使用也很方便,但其实真正想要讲好很复杂,更不要说写篇源码分析文章了。真的是努力理解设计者的思路,争取每行都能理解边界条件,但踩坑太少,应用场景太少,确实有很大的疏漏,希望大家能够理解,不要偏听一面之词。
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!