一个微信小程序_谈谈jQuery之Deferred源码剖析

摘要: 谈一谈jQuery之Deferred源代码分析 本文关键详细介绍了谈一谈jQuery之Deferred源代码分析,网编感觉挺好的,如今共享给大伙儿,也给大伙儿做下参照。一起追随网编回来看一下吧一、序...

谈谈jQuery之Deferred源码剖析       这篇文章主要介绍了谈谈jQuery之Deferred源码剖析,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

一、前言

大约在夏季,我们谈过ES6的Promise,其实在ES6前jQuery早就有了Promise,也就是我们所知道的Deferred对象,宗旨当然也和ES6的Promise一样,通过链式调用,避免层层嵌套,如下:

//jquery版本大于1.8
function runAsync(){
 var def = $.Deferred();
 setTimeout(function(){
 console.log('I am done');
 def.resolve('whatever');
 }, 1000);
 return def;
runAsync().then(function(msg){
 console.log(msg);//= 打印'whatever'
}).done(function(msg){
 console.log(msg);//= 打印'undefined'
});

注:从jQuery1.8版本开始,then方法会返回一个新的受限制的deferred对象,即deferred.promise()—后续源码解读中我们会更加全面地了解到。因此,上述代码done中会打印'undefined'。

好了,通过上述示例代码,短暂的回顾了jQuery的Deferred使用后,我们一起来看看jQuery是怎么实现Deferred,当然解读jQuery的版本是大于1.8。

二、jQuery之Deferred源码剖析

整体架构,如下:

jQuery.extend( {
 Deferred: function( func ) {
 var tuples = [
 // action, add listener, listener list, final state
 [ "resolve", "done", jQuery.Callbacks( "once memory" ), "resolved" ],
 [ "reject", "fail", jQuery.Callbacks( "once memory" ), "rejected" ],
 [ "notify", "progress", jQuery.Callbacks( "memory" ) ]
 state = "pending",
 promise = {
 state: function() {...},
 always: function() {...},
 then: function() {...},
 promise: function( obj ) {...}
 deferred = {};
 // Keep pipe pat
 promise.pipe = promise.then;
 // Add list-specific methods
 jQuery.each( tuples, function( i, tuple ) {} );
 // Make the deferred a promise
 promise.promise( deferred );
 // Call given func if any
 if ( func ) {
 func.call( deferred, deferred );
 // All done!
 return deferred;

整体架构上,如果你了解设计模式中的工厂模式,那么不难看出,jQuery.Deferred就是一个工厂,每次执行jQuery.Deferred时,都会返回一个加工好的deferred对象。

接下来,我们再一步一步剖析上述代码。

首先,是数组tuples:

tuples = [
 // action, add listener, listener list, final state
 [ "resolve", "done", jQuery.Callbacks( "once memory" ), "resolved" ],
 [ "reject", "fail", jQuery.Callbacks( "once memory" ), "rejected" ],
 [ "notify", "progress", jQuery.Callbacks( "memory" ) ]
]

tuples一开始就为我们预先定义了三种状态—‘resolved'、'rejected'以及'pending',以及它们所对应的一系列值和操作,值得注意的是每种状态中,都调用了一个jQuery.Callbacks方法,如下:

它是个什么玩意儿?

jQuery.Callbacks = function( options ) {
 // Convert options from String-formatted to Object-formatted if needed
 // (we check in cache first)
 options = typeof options === "string" 
 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 = options.once;
 // Execute callbacks for all pending executions,
 // respecting firingIndex overrides and runtime changes
 fired = firing = true;
 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
 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
 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 
 // 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
 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 = true;
 if ( !memory ) {
 self.disable();
 return this;
 locked: function() {
 return !!locked;
 // Call all callbacks with the given context and arguments
 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.Callbacks源码,如果你了解设计模式中的发布订阅者模式,不难发现,就是一个”自定义事件”嘛

所以,我们精简jQuery.Callbacks后,核心代码如下:

jQuery.Callbacks = function(){
 var list = [],
 self = {
 add: function(){/*添加元素到list*/},
 remove: function(){/*从list移除指定元素*/},
 fire: function(){/*遍历list并触发每次元素*/}
 return self;
}

一目了然,我们每执行一次jQuery.Callbacks方法,它就会返回一个独立的自定义事件对象。在tuples每个状态中执行一次jQuery.Callbacks,也就豁然开朗了—为每个状态提供一个独立的空间来添加、删除以及触发事件。

好了,关于变量tuples,我们就算大致解读完了。

state就是deferred对象的状态值嘛,我们可以通过deferred.state方法获取(稍后会见到)。

promise就是一个拥有state、always、then、promise方法的对象,每个方法详解如下:

promise = {
 state: function() {//返回状态值
 return state;
 always: function() {//不管成功还是失败,最终都会执行该方法
 deferred.done( arguments ).fail( arguments );
 return this;
 then: function( /* fnDone, fnFail, fnProgress */ ) {...},//重头戏,稍后会详讲
 promise: function( obj ) {//扩展promise,如不久我们会看见的promise.promise( deferred );
 return obj != null jQuery.extend( obj, promise ) : promise;
}

随后声明的一个空对象deferred。

promise.pipe=promise.then,就不累赘了,下面我们来看看jQuery.each(tuples, function(i, tuple){…})都干了什么,源码如下:

tuples = [
 [ "resolve", "done", jQuery.Callbacks( "once memory" ), "resolved" ],
 [ "reject", "fail", jQuery.Callbacks( "once memory" ), "rejected" ],
 [ "notify", "progress", jQuery.Callbacks( "memory" ) ]
jQuery.each( tuples, function( i, tuple ) {
 var list = tuple[ 2 ],
 stateString = tuple[ 3 ];
 // promise[ done | fail | progress ] = list.add
 promise[ tuple[ 1 ] ] = list.add;
 // Handle state
 if ( stateString ) {
 list.add( function() {
 // state = [ resolved | rejected ]
 state = stateString;
 // [ reject_list | resolve_list ].disable; progress_list.lock
 }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
 // deferred[ resolve | reject | notify ]
 deferred[ tuple[ 0 ] ] = function() {
 deferred[ tuple[ 0 ] + "With" ]( this === deferred promise : this, arguments );
 return this;
 deferred[ tuple[ 0 ] + "With" ] = list.fireWith;

通过jQuery.each遍历tuples数组,并对其进行相关操作,比如我们拿tuples数组中的第一个元素举例:

['resolve', 'done', jQuery.Callbacks('once memory'), 'resolved']

第一步、声明的变量list指向jQuery.Callbacks返回的对象,stateString取值为'resolved'

第二步、为promise添加'done'属性,并指向第一步中list.add(fail和progress即指向属于各自的自定义事件对象)

第三步、判断stateString值,如果为'resolved'或'rejected'状态,那么就添加三个事件函数到对应的list列表中:

--改变state状态的函数 --禁止对应状态的处理,如'resolved'后,那么必定不会触发rejected状态咯,反之亦然 --禁止pending状态,都'resolved'或者'rejected'了,那么deferred肯定不会处于pending状态咯

第四步、为对象deferred,添加触发各自状态('resolved','rejected','pending')的fire相关方法:

--resolve、resolveWith --reject、rejectWith --notify、notifyWith

好了,jQuery.each(tuples, function(i, tuple){…})解读就到此结束了。

总结:

通过jQuery.each遍历tuples,将tuples里的三种状态操作值done、fail以及progress添加到promise对象,并分别指向各自自定义对象中的add方法。如果状态为resolved或rejected,那么,再将三个特定函数添加到各自自定义对象的list列表下。随后,就是对deferred对象赋予三个状态各自的触发事件啦。

至此,promise、deferred对象如下图所示:

 

我们在前面讲解promise对象时,提到过它的promise属性,即为扩展promise对象,再回顾下:

 

所以接下来,源代码中的promise.promise(deferred),即为扩展deferred对象,让原来只有6个触发属性的deferred,同时拥有了promise对象的全部属性。

紧接着,func.call(deferred, deferred),即为执行参数func,当然,前提是func有值。值得注意的是,是将deferred作为func的执行对象以及执行参数的,这一点在promise.then中体现得淋淋尽致(稍后会细说)。

最后$.Deferred返回构建好的deferred对象。

到此,构建deferred整体流程走完。

三、细说promise.then

promise.then源码如下:

promise = {
 then: function( /* fnDone, fnFail, fnProgress */ ) { 
 var fns = arguments;
 return jQuery.Deferred( function( newDefer ) {
 jQuery.each( tuples, function( i, tuple ) {
 var fn = jQuery.isFunction( fns[ i ] ) fns[ i ];
 // deferred[ done | fail | progress ] for forwarding actions to newDefer
 deferred[ tuple[ 1 ] ]( function() {
 var returned = fn fn.apply( this, arguments );
 if ( returned jQuery.isFunction( returned.promise ) ) {
 returned.promise()
 .progress( newDefer.notify )
 .done( newDefer.resolve )
 .fail( newDefer.reject );
 } else {
 newDefer[ tuple[ 0 ] + "With" ](
 this === promise newDefer.promise() : this,
 fn [ returned ] : arguments
 } );
 } );
 fns = null;
 } ).promise();

精简promise.then的源码如下:

promise = {
 then: function( /* fnDone, fnFail, fnProgress */ ) { 
 var fns = arguments;
 return jQuery.Deferred( function( newDefer ) {
 } ).promise();

整体架构上,可以清晰的看出,promise.then方法最后通过jQuery.Deferred返回了一个新的受限制的deferred对象,即deferred.promise,正因为这样,所以执行完then方法后,我们是不能通过deferred.pomise手动触发resolve、reject或notify的。

接下来,我们再一步一步剖析promise.then源码。

var fns = arguments不过就是将then方法中的参数赋予fns,在接下来的jQuery.each里使用。接着,就通过jQuery.Deferred返回了一个构建好的deferred对象,但是注意,在jQuery.Deferred里有个参数—匿名函数,还记得在上一小节末尾处,我们说过如果jQuery.Deferred里有值,就执行它,并将构建好的deferred作为执行对象和参数传入么: 

固,promise.then方法中的newDefer指向通过jQuery.Deferred构建好的deferred。

紧接着,jQuery.each(tuples, function(i,tuple){…})处理,重点就是deferred[tuple[1]](function(){…});,注意,这里的deferred是then方法的父deferred哦,如下:

且tuple[1]为—done|fail|progress,在前面我们已经谈过,它们指向各自自定义事件对象的add方法。因此,也就明白了为什么deferred.resolve|reject|notify后,如果随后有then,会触发then方法的相关事件,如下:

但是,then方法后有then方法,又是怎么操作的呢?

它会判断then方法中的回调函数的返回值,如果是一个deferred对象,那么就将then方法自行创建的deferred对象中的相关触发事件,添加到回调函数中返回的deferred对象的对应的list列表中,这样,当我们触发回调函数中的相关触发事件后,也就会触发then方法的deferred对象了,从而,如果then方法后有then方法,也就关联了。

好了,那么如果then方法中的回调函数的返回值是一个非deferred对象呢?那么它就将这个返回值带上,直接触发then方法自行创建的deferred对象的相关事件,从而,如果then方法后有then方法,也就关联了。

好了,promise.then方法解决就算基本完毕。

四、思考

细细品来,大家有没有发现,其实promise.then就是通过作用域链,利用jQuery.Deferred中的变量deferred来关联父deferred的。如果,你还记得数据结构中的单链表,有没有发觉似曾相识呢,作者在这里通过jQuery.Deferred这个工厂构建每个deferred,然后利用作用域链相互关联,就如同单链表一样。

因此,借助这一思想,我们就一同模拟一个非常简单的Deferred,称作SimpleDef。主要作用就是每次我们执行SimpleDef函数,它都会返回一个构建好的simpleDef对象,该对象里面包含了三个方法done、then以及fire:

--done就如同add方法般,将done里的参数添加到它父simpleDef列表list中,并返回父simpleDef对象; --then就是将其参数func添加到父SimpleDef对象的列表list中,并返回一个新SimpleDef对象; --fire就是触发对应simpleDef对象的list列表里的所有函数。

实现代码如下:

 function SimpleDef(){
 var list = [],
 simpleDef = {
 done: function(func){
 list.push(func);
 return simpleDef;
 then: function(func){
 list.push(func);
 return SimpleDef();
 fire: function(){
 var i = list.length;
 while(i--){
 list[i]();
 return simpleDef;

测试代码如下: 

var def = SimpleDef();
var then1 = def.done(function(){
 console.log('self1-done1');
}).done(function(){
 console.log('self1-done2');
}).then(function(){
 console.log('self2-then1');
}).done(function(){
 console.log('self2-done1');
def.fire();//= self2-then1 self1-done2 self1-done1
console.log('xxxxxxxxxxxxxxxxxxxx');
then1.fire();//= self2-done1

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持凡科。




联系我们

全国服务热线:4000-399-000 公司邮箱:343111187@qq.com

  工作日 9:00-18:00

关注我们

官网公众号

官网公众号

Copyright?2020 广州凡科互联网科技股份有限公司 版权所有 粤ICP备10235580号 客服热线 400-000-8888