jquery 之 Deferred 使用与实现
观察者模式是开发中经常使用的模式,这个模式由两个主要部分组成:主题和观察者。通过观察者模式,实现主题和观察者的解耦。
主题负责发布内容,而观察者则接收主题发布的内容。通常情况下,观察者都是多个,所以,我们需要一个集合来保存所有的观察者,在主题发布内容之后,依次将主题发布的内容提供给观察者,从程序的角度来说,观察者就是一堆的方法,我们将内容作为参数依次调用这些方法。
如果了解 jQuery 的 Callbacks, 那么,你会发现,通过 Callbacks 来管理观察者的列表是很方便的事情。再添加一个主题,我们就可以实现观察者模式了。在 jQuery 中,这个模式被到处使用,不要说你没有使用过 ready 函数,回想一下,你可以在一个页面中,多次使用 ready 函数,在 ready 事件触发之后,这些函数就可以被依次调用了,这个机制就已经突破了简单的事件处理机制了。
需要说明的是,许多事件仅仅触发一次,比如 ready 事件,ajax 的请求处理等等,这种情况下,使用 Deferred 就非常方便了。
使用 Deferred
在 jQuery 中,实现观察者模式的就是 Deferred 了,我们先看它的使用。你也可以直接看 jQuery 的 Deferred 文档。
这个对象提供了主题和订阅的管理,使用它可以很容易实现一次性的观察者模式。
<code class="javascript">// 定义主题 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");</code>
通常我们在主题内部来决定什么时候,以及发布什么内容,而不允许在主题之外发布。通过 Deferred 对象的 promise 方法,我们可以只允许在主题之外注册观察者,有点像 .NET 中 event 的处理了。这样,我们的代码就成为下面的形式。
<code class="javascript">// 定义主题 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 );</code>
在 jQuery 中,甚至可以提供两个主题同时被观察, 需要注意的是,要等两个主题都触发之后,才会真正触发,每个观察者一次得到这两个主题,所以参数变成了 2 个。
<code class="javascript">// 定义主题 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 );</code>
实际上,在 jQuery 中,不仅可以发布成功完成的事件,主题还可以发布其它两种事件:失败和处理中。
失败事件,通过调用主题的 reject 方法可以发布失败的消息,对于观察者来说,需要通过 fail 来注册这个事件了。
处理中事件,通过调用主题的 notify 来发布处理中的消息,对于观察者来说,需要通过 progress 来注册这个事件。
要是观察者想一次性注册多个事件,那么,可以通过 then 来注册,这种方式可以处理主题的成功、失败和处理中三种事件。
<code class="javascript">$.get( "test.php" ).then( function() { alert( "$.get succeeded" ); }, function() { alert( "$.get failed!" ); } );</code>
只考虑成功和失败的话,就通过 always 来处理。
<code class="javascript">$.get( "test.php" ) .always(function() { alert( "$.get completed with success or error callback arguments" ); });</code>
jQuery 中 Deferred 的使用
常用的是 ajax, get, post 等等 Ajax 函数了。它们内部都已经实现为了 Deferred ,返回的结果就是 Deferred 对象了。也就是说你只管写观察者就可以了,主题内部已经处理好了,比如当 ajax 成功之后调用 resolve 来触发 done 事件并传递参数的问题。你可以继续使用传统的回调方式,显然推荐你使用 Deferred 方式了。这样你的代码结构更加清晰。
<code class="javascript">$.post( "test.php", { name: "John", time: "2pm" }) .done(function( data ) { alert( "Data Loaded: " + data ); });</code>
如果需要访问两个 Ajax ,则可以这样
<code class="javascript">$.when( $.post( "test.php", { name: "John", time: "2pm" }), $.post( "other.php" ) ) .done(function( data1, data2 ) { alert( "Data Loaded: " + data1 ); alert( "Data Loaded: " + data2 ); });</code>
实现 Deferred
通过前面的使用,其实你一定可以想到,在 Deferred 这个对象的内部,必须有三个回调队列了,这里的成功和失败只能一次完成,所以这两个 Callbacks 都使用了 once 来定义。
<code class="javascript">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") ] ],</code>
当前处理的状态。
<code class="javascript">state = "pending", promise = { state: function() { return state; },</code>
always 就是直接注册了两个事件。then 允许我们一次处理三种注册。
<code class="javascript">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(); },</code>
deferred 就是一个对象。pipe 是已经过时的用法,是 then 的别名。
<code class="javascript">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; },</code>
when 是一个助手方法,支持多个主题。
<code class="javascript">// 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 </code>
完整的代码如下所示:
<code class="javascript">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 </code>

Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics



Detailed explanation of jQuery reference method: Quick start guide jQuery is a popular JavaScript library that is widely used in website development. It simplifies JavaScript programming and provides developers with rich functions and features. This article will introduce jQuery's reference method in detail and provide specific code examples to help readers get started quickly. Introducing jQuery First, we need to introduce the jQuery library into the HTML file. It can be introduced through a CDN link or downloaded

How to use PUT request method in jQuery? In jQuery, the method of sending a PUT request is similar to sending other types of requests, but you need to pay attention to some details and parameter settings. PUT requests are typically used to update resources, such as updating data in a database or updating files on the server. The following is a specific code example using the PUT request method in jQuery. First, make sure you include the jQuery library file, then you can send a PUT request via: $.ajax({u

jQuery is a fast, small, feature-rich JavaScript library widely used in front-end development. Since its release in 2006, jQuery has become one of the tools of choice for many developers, but in practical applications, it also has some advantages and disadvantages. This article will deeply analyze the advantages and disadvantages of jQuery and illustrate it with specific code examples. Advantages: 1. Concise syntax jQuery's syntax design is concise and clear, which can greatly improve the readability and writing efficiency of the code. for example,

How to remove the height attribute of an element with jQuery? In front-end development, we often encounter the need to manipulate the height attributes of elements. Sometimes, we may need to dynamically change the height of an element, and sometimes we need to remove the height attribute of an element. This article will introduce how to use jQuery to remove the height attribute of an element and provide specific code examples. Before using jQuery to operate the height attribute, we first need to understand the height attribute in CSS. The height attribute is used to set the height of an element

Title: jQuery Tips: Quickly modify the text of all a tags on the page In web development, we often need to modify and operate elements on the page. When using jQuery, sometimes you need to modify the text content of all a tags in the page at once, which can save time and energy. The following will introduce how to use jQuery to quickly modify the text of all a tags on the page, and give specific code examples. First, we need to introduce the jQuery library file and ensure that the following code is introduced into the page: <

Title: Use jQuery to modify the text content of all a tags. jQuery is a popular JavaScript library that is widely used to handle DOM operations. In web development, we often encounter the need to modify the text content of the link tag (a tag) on the page. This article will explain how to use jQuery to achieve this goal, and provide specific code examples. First, we need to introduce the jQuery library into the page. Add the following code in the HTML file:

jQuery is a popular JavaScript library that is widely used to handle DOM manipulation and event handling in web pages. In jQuery, the eq() method is used to select elements at a specified index position. The specific usage and application scenarios are as follows. In jQuery, the eq() method selects the element at a specified index position. Index positions start counting from 0, i.e. the index of the first element is 0, the index of the second element is 1, and so on. The syntax of the eq() method is as follows: $("s

How to tell if a jQuery element has a specific attribute? When using jQuery to operate DOM elements, you often encounter situations where you need to determine whether an element has a specific attribute. In this case, we can easily implement this function with the help of the methods provided by jQuery. The following will introduce two commonly used methods to determine whether a jQuery element has specific attributes, and attach specific code examples. Method 1: Use the attr() method and typeof operator // to determine whether the element has a specific attribute
