最新文章专题视频专题问答1问答10问答100问答1000问答2000关键字专题1关键字专题50关键字专题500关键字专题1500TAG最新视频文章推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37视频文章20视频文章30视频文章40视频文章50视频文章60 视频文章70视频文章80视频文章90视频文章100视频文章120视频文章140 视频2关键字专题关键字专题tag2tag3文章专题文章专题2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章专题3
当前位置: 首页 - 科技 - 知识百科 - 正文

jquery之Deferred使用与实现

来源:动视网 责编:小采 时间:2020-11-27 20:32:32
文档

jquery之Deferred使用与实现

jquery之Deferred使用与实现:观察者模式是开发中经常使用的模式,这个模式由两个主要部分组成:主题和观察者。通过观察者模式,实现主题和观察者的解耦。 主题负责发布内容,而观察者则接收主题发布的内容。通常情况下,观察者都是多个,所以,我们需要一个集合来保存所有的观察者,在主
推荐度:
导读jquery之Deferred使用与实现:观察者模式是开发中经常使用的模式,这个模式由两个主要部分组成:主题和观察者。通过观察者模式,实现主题和观察者的解耦。 主题负责发布内容,而观察者则接收主题发布的内容。通常情况下,观察者都是多个,所以,我们需要一个集合来保存所有的观察者,在主


观察者模式是开发中经常使用的模式,这个模式由两个主要部分组成:主题和观察者。通过观察者模式,实现主题和观察者的解耦。

主题负责发布内容,而观察者则接收主题发布的内容。通常情况下,观察者都是多个,所以,我们需要一个集合来保存所有的观察者,在主题发布内容之后,依次将主题发布的内容提供给观察者,从程序的角度来说,观察者就是一堆的方法,我们将内容作为参数依次调用这些方法。

如果了解 jQuery 的 Callbacks, 那么,你会发现,通过 Callbacks 来管理观察者的列表是很方便的事情。再添加一个主题,我们就可以实现观察者模式了。在 jQuery 中,这个模式被到处使用,不要说你没有使用过 ready 函数,回想一下,你可以在一个页面中,多次使用 ready 函数,在 ready 事件触发之后,这些函数就可以被依次调用了,这个机制就已经突破了简单的事件处理机制了。

需要说明的是,许多事件仅仅触发一次,比如 ready 事件,ajax 的请求处理等等,这种情况下,使用 Deferred 就非常方便了。

使用 Deferred

在 jQuery 中,实现观察者模式的就是 Deferred 了,我们先看它的使用。你也可以直接看 jQuery 的 Deferred 文档。

这个对象提供了主题和订阅的管理,使用它可以很容易实现一次性的观察者模式。

// 定义主题
var subject = (function(){
 var dfd = $.Deferred();
 
 return dfd;
})();

// 两个观察者
var fn1 = function(content){
 console.log("fn1: " + content );
}

var fn2 = function(content){
 console.log("fn2: " + content );
}

// 注册观察者
$.when( subject )
.done( fn1 )
.done( fn2 );

// 发布内容
subject.resolve("Alice");

通常我们在主题内部来决定什么时候,以及发布什么内容,而不允许在主题之外发布。通过 Deferred 对象的 promise 方法,我们可以只允许在主题之外注册观察者,有点像 .NET 中 event 的处理了。这样,我们的代码就成为下面的形式。

// 定义主题
var subject = (function(){
 var dfd = $.Deferred();
 
 var task = function()
 {
 // 发布内容
 dfd.resolve("Alice");
 }
 
 setTimeout( task, 3000);
 
 return dfd.promise();
})();

// 两个观察者
var fn1 = function(content){
 console.log("fn1: " + content );
}

var fn2 = function(content){
 console.log("fn2: " + content );
}

// 注册观察者
$.when( subject )
.done( fn1 )
.done( fn2 );

在 jQuery 中,甚至可以提供两个主题同时被观察, 需要注意的是,要等两个主题都触发之后,才会真正触发,每个观察者一次得到这两个主题,所以参数变成了 2 个。

// 定义主题
var subjectAlice = (function(){
 var dfd = $.Deferred();
 
 var task = function()
 {
 // 发布内容
 dfd.resolve("Alice");
 }
 
 setTimeout( task, 3000);
 
 return dfd.promise();
})();

var subjectTom = (function(){
 var dfd = $.Deferred();
 
 var task = function()
 {
 // 发布内容
 dfd.resolve("Tom");
 }
 
 setTimeout( task, 1000);
 
 return dfd.promise();
})();

// 两个观察者
var fn1 = function(content1, content2){
 console.log("fn1: " + content1 );
 console.log("fn1: " + content2 );
}

var fn2 = function(content1, content2){
 console.log("fn2: " + content1 );
 console.log("fn2: " + content2 );
}

// 注册观察者
$.when( subjectAlice, subjectTom )
.done( fn1 )
.done( fn2 );

实际上,在 jQuery 中,不仅可以发布成功完成的事件,主题还可以发布其它两种事件:失败和处理中。

失败事件,通过调用主题的 reject 方法可以发布失败的消息,对于观察者来说,需要通过 fail 来注册这个事件了。

处理中事件,通过调用主题的 notify 来发布处理中的消息,对于观察者来说,需要通过 progress 来注册这个事件。

要是观察者想一次性注册多个事件,那么,可以通过 then 来注册,这种方式可以处理主题的成功、失败和处理中三种事件。

$.get( "test.php" ).then(
 function() {
 alert( "$.get succeeded" );
 }, function() {
 alert( "$.get failed!" );
 }
);

只考虑成功和失败的话,就通过 always 来处理。

$.get( "test.php" )
.always(function() {
 alert( "$.get completed with success or error callback arguments" );
});

jQuery 中 Deferred 的使用

常用的是 ajax, get, post 等等 Ajax 函数了。它们内部都已经实现为了 Deferred ,返回的结果就是 Deferred 对象了。也就是说你只管写观察者就可以了,主题内部已经处理好了,比如当 ajax 成功之后调用 resolve 来触发 done 事件并传递参数的问题。你可以继续使用传统的回调方式,显然推荐你使用 Deferred 方式了。这样你的代码结构更加清晰。

$.post( "test.php", { name: "John", time: "2pm" })
 .done(function( data ) {
 alert( "Data Loaded: " + data );
 });

如果需要访问两个 Ajax ,则可以这样

$.when( $.post( "test.php", { name: "John", time: "2pm" }),
 $.post( "other.php" ) )
 .done(function( data1, data2 ) {
 alert( "Data Loaded: " + data1 );
 alert( "Data Loaded: " + data2 );
 });

实现 Deferred

通过前面的使用,其实你一定可以想到,在 Deferred 这个对象的内部,必须有三个回调队列了,这里的成功和失败只能一次完成,所以这两个 Callbacks 都使用了 once 来定义。

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 就是直接注册了两个事件。then 允许我们一次处理三种注册。

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 action = tuple[ 0 ],
 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[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
 }
 });
 });
 fns = null;
 }).promise();
 },

deferred 就是一个对象。pipe 是已经过时的用法,是 then 的别名。

deferred = {};

 // Keep pipe for back-compat
 promise.pipe = promise.then;

 // Add list-specific methods
 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;
 });

 // Make the deferred a promise
 promise.promise( deferred );

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

 // All done!
 return deferred;
},

when 是一个助手方法,支持多个主题。

// Deferred helper
when: function( subordinate /* , ..., subordinateN */ ) {
 var i = 0,
 resolveValues = core_slice.call( arguments ),
 length = resolveValues.length,

 // the count of uncompleted subordinates
 remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,

 // the master Deferred. If resolveValues consist of only a single Deferred, just use that.
 deferred = remaining === 1 ? subordinate : jQuery.Deferred(),

 // Update function for both resolve and progress values
 updateFunc = function( i, contexts, values ) {
 return function( value ) {
 contexts[ i ] = this;
 values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value;
 if( values === progressValues ) {
 deferred.notifyWith( contexts, values );
 } else if ( !( --remaining ) ) {
 deferred.resolveWith( contexts, values );
 }
 };
 },

 progressValues, progressContexts, resolveContexts;

 // add listeners to Deferred subordinates; treat others as resolved
 if ( length > 1 ) {
 progressValues = new Array( length );
 progressContexts = new Array( length );
 resolveContexts = new Array( length );
 for ( ; i < length; i++ ) {
 if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
 resolveValues[ i ].promise()
 .done( updateFunc( i, resolveContexts, resolveValues ) )
 .fail( deferred.reject )
 .progress( updateFunc( i, progressContexts, progressValues ) );
 } else {
 --remaining;
 }
 }
 }

 // if we're not waiting on anything, resolve the master
 if ( !remaining ) {
 deferred.resolveWith( resolveContexts, resolveValues );
 }

 return deferred.promise();
}

完整的代码如下所示:

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() {
 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 action = tuple[ 0 ],
 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[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
 }
 });
 });
 fns = null;
 }).promise();
 },
 // Get a promise for this deferred
 // If obj is provided, the promise aspect is added to the object
 promise: function( obj ) {
 return obj != null ? jQuery.extend( obj, promise ) : promise;
 }
 },
 deferred = {};

 // Keep pipe for back-compat
 promise.pipe = promise.then;

 // Add list-specific methods
 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;
 });

 // Make the deferred a promise
 promise.promise( deferred );

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

 // All done!
 return deferred;
 },

 // Deferred helper
 when: function( subordinate /* , ..., subordinateN */ ) {
 var i = 0,
 resolveValues = core_slice.call( arguments ),
 length = resolveValues.length,

 // the count of uncompleted subordinates
 remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,

 // the master Deferred. If resolveValues consist of only a single Deferred, just use that.
 deferred = remaining === 1 ? subordinate : jQuery.Deferred(),

 // Update function for both resolve and progress values
 updateFunc = function( i, contexts, values ) {
 return function( value ) {
 contexts[ i ] = this;
 values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value;
 if( values === progressValues ) {
 deferred.notifyWith( contexts, values );
 } else if ( !( --remaining ) ) {
 deferred.resolveWith( contexts, values );
 }
 };
 },

 progressValues, progressContexts, resolveContexts;

 // add listeners to Deferred subordinates; treat others as resolved
 if ( length > 1 ) {
 progressValues = new Array( length );
 progressContexts = new Array( length );
 resolveContexts = new Array( length );
 for ( ; i < length; i++ ) {
 if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
 resolveValues[ i ].promise()
 .done( updateFunc( i, resolveContexts, resolveValues ) )
 .fail( deferred.reject )
 .progress( updateFunc( i, progressContexts, progressValues ) );
 } else {
 --remaining;
 }
 }
 }

 // if we're not waiting on anything, resolve the master
 if ( !remaining ) {
 deferred.resolveWith( resolveContexts, resolveValues );
 }

 return deferred.promise();
 }
});

文档

jquery之Deferred使用与实现

jquery之Deferred使用与实现:观察者模式是开发中经常使用的模式,这个模式由两个主要部分组成:主题和观察者。通过观察者模式,实现主题和观察者的解耦。 主题负责发布内容,而观察者则接收主题发布的内容。通常情况下,观察者都是多个,所以,我们需要一个集合来保存所有的观察者,在主
推荐度:
标签: 使用 实现 jQuery
  • 热门焦点

最新推荐

猜你喜欢

热门推荐

专题
Top