js-jQuery中的Callbacks、Deferred和When对象详解(1)

6 篇文章 0 订阅

终于抽出点时间来讲解一下jQuery中的Callbacks/Deferred这几个对象了,不过篇幅可能过长,讲到哪儿是哪儿吧。

$.Deferred方法每次调用都会返回一个Deferred对象。Deferred对象用于异步方法链式调用,让编程方式从传统的回调函数中解脱出来——javascript的回调深度一直都是遭人诟病。

首先从最底层Callbacks对象讲起,看简化后的源码:

Callbacks: function(options){
	var list = [],
		firing, fired, memory,
		firingIndex, firingStart, firingLength,
		options = options || {},
		stack = !options.once && [],
		fire = function(data){
			memory = options.memory && data;
			firingIndex = firingStart || 0;
			firingStart = 0;
			fired = true;
			firingLength = list.length;
			firing = true;
			for(; firingIndex < firingLength; firingIndex++){
				if(list[firingIndex].apply(data[0], data[1]) === false && options.stopOnFalse === true){
					memory = false; // To prevent further calls using add
					break;
				}
			}
			firing = false;
			if(list){
				if(stack){
					if(stack.length){
						fire(stack.shift());
					}
				}else if(memory){
					list = [];
				//if options.once then disable it.
				}else{
					self.disable();
				}
			}
		},
		self = {
			add: function(){
				if(list){
					var start = list.length;
					(function _add(args){
						iBen.each(args, function(arg){
							if(jQuery.isFunction(arg)){
								list.push(arg);
							} else if ( arg && arg.length && !jQuery.isString(arg) ) {
								// Inspect recursively
								_add( arg );
							}
						});
					})(arguments);
					if(firing){
						firingLength = list.length;
					}else if(memory){
						firingStart = start;
						fire(memory);
					}
				}
				return this;
			},
			// Remove a callback from the list
			remove: function() {
				if ( list ) {
					jQuery.each( arguments, function( _, arg ) {
						var index;
						while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
							list.splice( index, 1 );
							// Handle firing indexes
							if ( firing ) {
								if ( index <= firingLength ) {
									firingLength--;
								}
								if ( index <= firingIndex ) {
									firingIndex--;
								}
							}
						}
					});
				}
				return this;
			},
			fireWith: function(context, args){
				if(list){
					args = [context, args];
					if(firing){
						stack.push(args);
					}else{
						fire(args);
					}
				}
				return this;
			},
			fire: function(){
				return this.fireWith(this, arguments);
			},
			disable: function(){
				list = stack = memory = undefined;
				return this;
			}
		};
	return self;
}

我就不逐行写注释了,简单来说最后返回的self才是真正的Callbacks对象,公布出的接口主要也就是add、fire、fireWith、remove这几个方法。

add方法将函数添加到回调列表list中。

_add方法在参数是数组的时候会递归调用。

如果正在调用的话,修正回调列表的长度。

如果不是调用中且没有指明once选项同时指定了memory选项去记住最后一次调用的参数时则修正开始调用索引去立即调用。

fire方法实际调用的是Callbacks中的fire方法。

调用fireWith-指定默认环境为self和参数。

将参数进行格式化,如果正在调用则放到stack中等待当前调用列表完成后调用,否则立即调用。

进行fire时会通过options.once判断改列表是否只调用一次,是则调用结束后disable掉该回调列表,否则查看stack中是否有函数等待回调,如果指定了memory选项去记录了最后一次调用的参数,则清空该回调列表。

remove方法将对应的函数从回调列表中移除,并修正firing的索引。


整个Callbacks的流程就是这样,其实Callbacks可以看做为一个观察者模式。看观察者模式的代码:

observer : (function(box){
	var Publisher={
		subscribe: function(fn, context, type){
			type = type || 'any';
			fn = iBen.isFunction(fn) ? fn: context[fn];
			if (iBen.isUndefined(this.subscribers[type]))
				this.subscribers[type] = [];
			
			this.subscribers[type].push([fn, context]);
		},
		unsubscribe: function(fn, type){
			type = type || 'any';
			var that = this;
			this.subscribers[type].splice($A(this.subscribers[type]).indexOf(fn), 1);
		},
		shutdown: function(type){
			type = type || 'any';
			this.subscribers[type] = [];
		},
		publish: function(){
			var args, type = arguments[arguments.length-1];
			type = iBen.isString(type)? type : 'any';
			args = _slice.call(arguments, 0);
			this.subscribers[type]&&iBen.each(this.subscribers[type], function(object){
				object[0].apply(object[1], args);
			});
		}
	};
	return function(o){
		o = o || {};
		iBen.extend(o, Publisher, true, 'function');
		o.subscribers={any : []};
		return o;
	};
})()
基本上工作的流程是一样的,都是将对应的事件添加到队列中去,然后通过fire/publish的方式调用整个回调列表,除了Callbacks对象可以在函数的执行中通过self对象提供的接口更细粒度的控制和获取回调列表的运行状态。

Callbacks对象相对比较直观,下面看jQuery的Deferred对象,不过在这之前最好先自行了解一下Promise/A+规范

要求

Promise的状态

一个 Promise 必须处于等待态(Pending)执行态(Fulfilled)拒绝态(Rejected)这三种状态中的一种之中。

  1. 处于等待态时,promise :

    • 可以迁移至执行态或拒绝态
  2. 处于执行态时,promise :

    • 不能迁移至其他任何状态
    • 必须拥有一个不可变的值
  3. 处于拒绝态时,promise

    • 不能迁移至其他任何状态
    • 必须拥有一个不可变的据因

这里的不可变指的是恒等(即可用 === 判断相等),而不是意味着更深层次的不可变

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() {
				return state;
			},
			always: function() {
				deferred.done( arguments ).fail( arguments );
				return this;
			},
			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()
									.done( newDefer.resolve )
									.fail( newDefer.reject )
									.progress( newDefer.notify );
							} else {
								newDefer[ tuple[ 0 ] + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
							}
						});
					});
					fns = null;
				}).promise();
			},
			
			promise: function( obj ) {
				return obj != null ? jQuery.extend( obj, promise ) : promise;
			}
		},
		deferred = {};

	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;
	});

	promise.promise( deferred );

	if ( func ) {
		func.call( deferred, deferred );
	}

	// All done!
	return deferred;
}

可以看出,jQuery并没有完全按照Promise规范来构建promise对象。jQuery的三种状态分别为:resolved,rejected和处理中状态,而对应promise规范中的执行状态和被拒绝状态时状态不可改变的要求:
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 );
			}
i^1会讲0和1的索引对调(在我的另一篇博文中有提到),在执行resolve时会将reject列表disable掉,同样在执行reject中会disable掉resolve列表,并将notify列表lock住,以防止状态的改变。

Deferred对象里面主要包含两个部分:

1、promise对象

在遍历tuples时会将对应的Callbacks对象的add方法作为promise的done | fail | progress。

2、deferred对象

在遍历tuples时会将对应的Callbacks对象的fireWith方法作为resolveWith | rejectWith | notifyWith

调用promise.promise将deferred对象做成一个promise对象。

最后返回deferred对象。

大概讲到这里吧,剩下的then方法和when对象留到下次讲解。

有其他疑问或错误之处欢迎之处~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
jQuery打印插件 jQuery.print是一个用于打印页面特定部分的插件 用法 导入jQuery后将其包含在HTML,如: < script type = “ text / JavaScript ” src = “ path / to / jquery.print.js ” > </ script > 使用它像: $(“#myElementId ”)。print(/ * options * /); 要么 $。print(“#myElementId ” / *,options * /); 您可以提交选项对象,如: $(“#myElementId ”)。print({ globalStyles : 是的, mediaPrint : false, stylesheet : null, noPrintSelector : “。 no -print ”, iframe : 是的, append : null, 前置: null, manualCopyFormValues : true, 延期: $。延期(), 超时: 750, title : null, doctype : ' <!doctype html> ' }); 目前,此插件支持以下选项: globalStyles 默认: true 可接受的值:布尔值 功能:是否应包含父文档的样式 mediaPrint 默认: false 可接受的值:布尔值 功能:是否应包含带有media ='print'的链接标签; 由globalStyles选项覆盖 样式表 默认: null 可接受的值:URL字符串 功能:要包括的外部样式表的URL noPrintSelector 默认: ".no-print" 可接受的值:任何有效 jQuery-selector 功能:要从打印排除的项目的选择器 IFRAME 默认值:true,如果传递no-vaild iframe选择器,则创建隐藏的iframe 可接受的值:任何有效jQuery-selector或布尔值 功能:是否从iframe打印而不是弹出窗口; 可以将jQuery-selector现有iframe作为值 附加/添加 默认: null 可接受的值:任何有效jQuery-selector或HTML文本 功能:在选定内容之前(前置)或之后(追加)添加自定义HTML manuallyCopyFormValues 默认: true 可接受的值:布尔值 功能:是否应将用户更新的表单输入值复制到打印的标记上(这可以通过手动迭代每个表单元素来完成) 延期的 默认: $.Deferred() 可接受的值:任何有效的jQuery.Deferred对象 功能:一旦调用print函数就解析的jQuery.Deferred对象。可用于设置回调 - 请参阅wiki 超时 默认: 750 可接受的值:以毫秒为单位的时间 setTimeout 功能:在创建新窗口/ iframe之前更改等待内容等加载内容的最大时间量,如果新窗口/ iframe 的load事件尚未触发,则作为后备 标题 默认值:null,使用主页标题 可接受的值:任何单行字符串 功能:更改打印的标题 DOCTYPE 默认: '<!doctype html>' Acceptable-Values:任何有效的doctype字符串 功能:将doctype添加到打印的文档框架

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值