PS:今天上午,非常郁闷,有很多简略基础的问题搞得我有些迷茫,哎,码代几天不写就忘。目前又不当COO,还是得用心记码代哦!
由于jQuery ajax对Callbacks、Deferred、serialize、event等模块的赖依,议建对这些模块没有识认的友朋看一下jQuery Callbacks、jQuery Deferred、jQuery serialize、jQuery event(上)、jQuery event(下)。
这篇文章重要分析的是具有380+行的jQuery.ajax数函,该数函是jQuery ajax的心核数函,jQuery的其他ajax方法几乎都是基于该方法的。
上一篇文章我们了解了Baidu ajax(当然是旧版的,还是被简化的……),那么我们想给这个简略的ajax方法加添什么功能呢?
可链式操纵
既然是jQuery必定先要决解的是链式操纵的问题。
jQuery中的Deferred可以实现异步链式模型实现,Promise对象可以易轻的定绑胜利、失败、行进中三种状态的回调数函,然后通过在状态码在来回调不同的数函就好了。
但仅仅回返一个promise没什么用
promise只能处置异步,我们须要回返一个更有效的货色。
jqXHR就是这样的货色,实际上他是一个寨山版的XHR对象。
这样我们就可以展扩一下XHR对象的功能, 供提定一的容错处置,露暴给外部供提一些方法。
// 假货xhr,或者说寨山xhr……╮(╯▽╰)╭ // 为了可以实现链式操纵 // 便顺展扩一下xhr对象功能 // 还有供提定一的容错处置 jqXHR = { // 预备状态 readyState: 0, // 如果须要,建创一个响应头参数的表 getResponseHeader: function( key ) { var match; // 如果状态为2,状态2示表ajax成完 if ( state === 2 ) { // 如果没有响应头 if ( !responseHeaders ) { // 响应头设空 responseHeaders = {}; while ( (match = rheaders.exec( responseHeadersString )) ) { // 组装响应头 responseHeaders[ match[1].toLowerCase() ] = match[ 2 ]; } } // 响应头对应的key的值 match = responseHeaders[ key.toLowerCase() ]; } // 回返 return match == null ? null : match; }, // 回返响应头字符串 getAllResponseHeaders: function() { // 看看否是接收到了,接收到直接回返,否则为null return state === 2 ? responseHeadersString : null; }, // 设置请求头 setRequestHeader: function( name, value ) { var lname = name.toLowerCase(); // 如果state不为0 if ( !state ) { // 如果requestHeadersNames[ lname ]不为空, // 则requestHeadersNames[ lname ]稳定,name设置为该值 // 否则,requestHeadersNames[ lname ]不空, // 则requestHeadersNames[ lname ]设置为name, // 该射映关系用于免避户用巨细写书写错误之类的问题,容错处置 name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name; // 在现的name是对的,或者是第一次设置这个name,不须要容错 // 设置请求头对应值 requestHeaders[ name ] = value; } return this; }, // 重写响应头content-type overrideMimeType: function( type ) { if ( !state ) { s.mimeType = type; } return this; }, // 对应状态的回调数函集 statusCode: function( map ) { var code; // 如果map存在,预备组装 if ( map ) { // 如果状态小于2,示表旧的回调可能还没有效到 if ( state < 2 ) { // 历遍map面里的有所code for ( code in map ) { // 用似类链表的方法加添,以证保旧的回调仍然存在 statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; } // 状态大于2,证明经已成完了 } else { // 无论Deferred胜利还是失败都执行前当状态回调 jqXHR.always( map[ jqXHR.status ] ); } } return this; }, // 中断请求 abort: function( statusText ) { var finalText = statusText || strAbort; // 可以先懂得成XHR对象,当然这也不是真正的XHR对象 if ( transport ) { transport.abort( finalText ); } // 调用done,示表干完了 done( 0, finalText ); return this; } };
可是这还没有链式啊!
怎么把jqXHR成变一个Promise呢?
让一个对象具有另一个对象的方法,大家会想到什么呢?
承继?
不,直接插上去就好了……这里就现体了Javascript“易插拔”的点特……自己乱起的……囧rz……
应该说动态、弱类的点特。
什么意思?就是将Promise上的方法插到jqXHR对象上就好了。
// 在jqXHR粘上promise的有所方法,此时jqXHR就很像一个promise了 // 实际上就是用jQuery.extend(jqXHR, promise)而已 // jqXHR刚寨山了xhr对象,又开始寨山promise对象了 // 便顺把jqXHR的complete绑上completeDeferred.add // 意思是jqXHR状态成完后调用completeDeferred这个Callbacks排队 // 话说promise面里并没有complete这个方法 // 面后我们可以看到,作者大人又要给jqXHR插上complete、success、error方法 // Javascript就是这样简略,即插即用……囧rz deferred.promise( jqXHR ).complete = completeDeferred.add; // 定绑jqXHR.success为promise面里的done方法 jqXHR.success = jqXHR.done; // 定绑jqXHR.error为promise面里的fail方法 jqXHR.error = jqXHR.fail;
这模样直接插上去在强类语言中是做不到的,当然也可以用组合模式来实现一个对象具有多个对象的方法,但是却没法动态的去给对象加添各种方法。
强类语言会制约对象子辈一只能“会”那么几种“方法”,Javascript的对象可以在生命周期内意随“习学”各种“方法”。
所以说,强类语言和Javascript的对象模型是有差异的,将设计模式套硬搬生进Javascript或许是错误的。
我们再举个例子,比如如果用所谓的Javascript组合模式来重写面上的码代可能会是这样的:
大家认为哪个好呢?// 义定局部变量deferred和completeDeferred function JQXHR(){ // 义定jqXHR的属性和方法 for(i in deferred.promise){ this[i] = this.promise[i]; } this.complete = completeDeferred.add; this.success = this.done; this.error = this.done } var jqXHR = new JQXHR();成变面上的模样重要是因为要决解上面两个问题:
- jqXHR由于是露暴在外的,他不能包括deferred和completeDeferred,不许允户用在外部操纵这两个对象的状态。
- deferred和completeDeferred也不能成为jqXHR的私有变量,因为还要更具ajax事件发触。
供提全局事件,使得UI可以根据ajax状态行进转变
这里重要利用了jQuery.event.trigger和jQuery.fn.trigger模拟发事件。
// 如果须要,而且全局事件没有被发触过 if ( fireGlobals && jQuery.active++ === 0 ) { // 则通过jQuery.event.trigger模拟发触 jQuery.event.trigger("ajaxStart"); }
当ajax开始时模拟全局事件,ajaxStart。
// 如果须要,对特定对象发触全局事件ajaxSend if ( fireGlobals ) { globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); }
ajax发送消息,发触ajaxSend。
// 如果须要发触全局事件 if ( fireGlobals ) { // 对指定元素发触事件ajaxSuccess或者ajaxError globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", [ jqXHR, s, isSuccess ? success : error ] ); } // 回调成完后的Callbacks队列 completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); // 如果须要发触全局事件 if ( fireGlobals ) { // 对指定元素发触事件ajaxComplete globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); // 该ajax发触终了,标记active减1,如果为0,证明有所ajax束结 if ( !( --jQuery.active ) ) { // 发触ajaxStop事件 jQuery.event.trigger("ajaxStop"); } }
束结时候发触ajaxSuccess或ajaxError,再发出ajaxComplete,如果全体ajax束结则发触ajaxStop。
否是许允应用存缓数据
// 如果不须要content // 看看需不须要加其他信息 if ( !s.hasContent ) { // 如果data存在,那么经已序列化 if ( s.data ) { // 加添到cacheURL中 cacheURL = ( s.url += ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) + s.data ); // 删除掉则面后就不会被发送出去了 delete s.data; } // 看看否是须要免避数据从存缓中读取 if ( s.cache === false ) { s.url = rts.test( cacheURL ) ? // 如果经已有_参数,那么设置他的值 cacheURL.replace( rts, "$1_=" + ajax_nonce++ ) : // 否则加添一个_ = xxx在URL面后 cacheURL + ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ajax_nonce++; } }
通过给地址附加参数_=xxx来免避存缓。
// 看看需不须要设置If-Modified-Since和If-None-Match头信息 if ( s.ifModified ) { if ( jQuery.lastModified[ cacheURL ] ) { jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); } if ( jQuery.etag[ cacheURL ] ) { jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); } }
以及设置If-Modified-Since和If-None-Match头信息。
// 看看否是须要存缓置If-Modified-Since和If-None-Match头 if ( s.ifModified ) { // 取得Last-Modified modified = jqXHR.getResponseHeader("Last-Modified"); // 如果Last-Modified存在 if ( modified ) { // 在jQuery.lastModified[cacheURL]保存Last-Modified jQuery.lastModified[ cacheURL ] = modified; } // 取得etag modified = jqXHR.getResponseHeader("etag"); // 如果etag存在 if ( modified ) { // 在jQuery.etag[cacheURL]存缓etag jQuery.etag[ cacheURL ] = modified; } }
存缓If-Modified-Since和If-None-Match头信息。
设置超时
// 如果是异步,并且设置了超时 if ( s.async && s.timeout > 0 ) { // 设置超时 timeoutTimer = setTimeout(function() { jqXHR.abort("timeout"); }, s.timeout ); }
重要功能分析完了,完整备注码代见下。
完整备注
jQuery.ajax = function( url, options ) { // 如果url是一个obj,模拟1.5版本以前的方法 if ( typeof url === "object" ) { options = url; url = undefined; } // 设置options options = options || {}; var transport, // 存缓cacheURL cacheURL, // 响应头 responseHeadersString, responseHeaders, // 超时控制器 timeoutTimer, // 跨域判定变量 parts, // 否是须要发触全局事件 fireGlobals, // 循环变量 i, // 通过jQuery.ajaxSetup改造参数对象 s = jQuery.ajaxSetup( {}, options ), // 回调指定上下文,也就是他的this callbackContext = s.context || s, // 全局事件中的响应数函的指定上下文 // 有s.context,且是DOM节点,或者jQuery收集器 globalEventContext = s.context && ( callbackContext.nodeType || callbackContext.jquery ) ? // 通过jQuery包装 jQuery( callbackContext ) : // 否则为jQuery.event jQuery.event, // 新建一个deferred deferred = jQuery.Deferred(), // deferred成完后的Callbacks队列 completeDeferred = jQuery.Callbacks("once memory"), // 对应状态的回调数函集 statusCode = s.statusCode || {}, // 请求头 requestHeaders = {}, requestHeadersNames = {}, // 包装类jqXHR的状态 state = 0, // 默认中断消息 strAbort = "canceled", // 假货xhr,或者说寨山xhr……╮(╯▽╰)╭ // 为了可以实现链式操纵 // 便顺展扩一下xhr对象功能 // 还有供提定一的容错处置 jqXHR = { // 预备状态 readyState: 0, // 如果须要,建创一个响应头参数的表 getResponseHeader: function( key ) { var match; // 如果状态为2,状态2示表ajax成完 if ( state === 2 ) { // 如果没有响应头 if ( !responseHeaders ) { // 响应头设空 responseHeaders = {}; while ( (match = rheaders.exec( responseHeadersString )) ) { // 组装响应头 responseHeaders[ match[1].toLowerCase() ] = match[ 2 ]; } } // 响应头对应的key的值 match = responseHeaders[ key.toLowerCase() ]; } // 回返 return match == null ? null : match; }, // 回返响应头字符串 getAllResponseHeaders: function() { // 看看否是接收到了,接收到直接回返,否则为null return state === 2 ? responseHeadersString : null; }, // 存缓请求头 setRequestHeader: function( name, value ) { var lname = name.toLowerCase(); // 如果state不为0 if ( !state ) { // 如果requestHeadersNames[ lname ]不为空, // 则requestHeadersNames[ lname ]稳定,name设置为该值 // 否则,requestHeadersNames[ lname ]不空, // 则requestHeadersNames[ lname ]设置为name, // 该射映关系用于免避户用巨细写书写错误之类的问题,容错处置 name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name; // 在现的name是对的,或者是第一次设置这个name,不须要容错 // 设置请求头对应值 requestHeaders[ name ] = value; } return this; }, // 重写响应头content-type overrideMimeType: function( type ) { if ( !state ) { s.mimeType = type; } return this; }, // 对应状态的回调数函集 statusCode: function( map ) { var code; // 如果map存在,预备组装 if ( map ) { // 如果状态小于2,示表旧的回调可能还没有效到 if ( state < 2 ) { // 历遍map面里的有所code for ( code in map ) { // 用似类链表的方法加添,以证保旧的回调仍然存在 statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; } // 状态大于2,证明经已成完了 } else { // 无论Deferred胜利还是失败都执行前当状态回调 jqXHR.always( map[ jqXHR.status ] ); } } return this; }, // 中断请求 abort: function( statusText ) { var finalText = statusText || strAbort; // 可以先懂得成XHR对象,当然这也不是真正的XHR对象 if ( transport ) { transport.abort( finalText ); } // 调用done,示表干完了 done( 0, finalText ); return this; } }; // 在jqXHR粘上promise的有所方法,此时jqXHR就很像一个promise了 // 实际上就是用jQuery.extend(jqXHR, promise)而已 // jqXHR刚寨山了xhr对象,又开始寨山promise对象了 // 便顺把jqXHR的complete绑上completeDeferred.add // 意思是jqXHR状态成完后调用completeDeferred这个Callbacks排队 // 话说promise面里并没有complete这个方法 // 面后我们可以看到,作者大人又要给jqXHR插上complete、success、error方法 // Javascript就是这样简略,即插即用……囧rz deferred.promise( jqXHR ).complete = completeDeferred.add; // 定绑jqXHR.success为promise面里的done方法 jqXHR.success = jqXHR.done; // 定绑jqXHR.error为promise面里的fail方法 jqXHR.error = jqXHR.fail; // 确定url参数,否则用前当地址。将地址的#号面后的有所货色去掉 // 比如http://127.0.0.1#main,去掉这个#main // 如果开头是//,及数据传输协议没有,那么用前当页面的数据传输协议替换 // 比如//127.0.0.1,成变http://127.0.0.1 s.url = ( ( url || s.url || ajaxLocation ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" ); // 义定type,向后兼容 s.type = options.method || options.type || s.method || s.type; // 取出数据类型列表 // 没有则为"*", // 有则认为是用空格分隔的字符串,将其成变数组 s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().match( core_rnotwhite ) || [""]; // 当协议、主机、端口和前当不匹配时,证明这是一个跨域请求 if ( s.crossDomain == null ) { // 分隔前当url parts = rurl.exec( s.url.toLowerCase() ); // 判定否是是跨域 s.crossDomain = !!( parts && ( parts[ 1 ] !== ajaxLocParts[ 1 ] || parts[ 2 ] !== ajaxLocParts[ 2 ] || ( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) != ( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? 80 : 443 ) ) ) ); } // 如果data经已是一个字符串了,那么就不用转换了 if ( s.data && s.processData && typeof s.data !== "string" ) { // 序列化 s.data = jQuery.param( s.data, s.traditional ); } // 预过滤 inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); // 如果请求被prefilter终止,则退出 if ( state === 2 ) { return jqXHR; } // 看看需不须要发触全局事件 fireGlobals = s.global; // 如果须要,而且全局事件没有被发触过 if ( fireGlobals && jQuery.active++ === 0 ) { // 则通过jQuery.event.trigger模拟发触 jQuery.event.trigger("ajaxStart"); } // 将类型大写 s.type = s.type.toUpperCase(); // 判断需不须要设置content s.hasContent = !rnoContent.test( s.type ); // 存缓URL,用来在之后设置If-Modified-Since和If-None-Match cacheURL = s.url; // 如果不须要content // 看看需不须要加其他信息 if ( !s.hasContent ) { // 如果data存在,那么经已序列化 if ( s.data ) { // 加添到cacheURL中 cacheURL = ( s.url += ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) + s.data ); // 删除掉则面后就不会被发送出去了 delete s.data; } // 看看否是须要免避数据从存缓中读取 if ( s.cache === false ) { s.url = rts.test( cacheURL ) ? // 如果经已有_参数,那么设置他的值 cacheURL.replace( rts, "$1_=" + ajax_nonce++ ) : // 否则加添一个_ = xxx在URL面后 cacheURL + ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ajax_nonce++; } } // 看看需不须要设置If-Modified-Since和If-None-Match头信息 if ( s.ifModified ) { if ( jQuery.lastModified[ cacheURL ] ) { jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); } if ( jQuery.etag[ cacheURL ] ) { jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); } } // 如果数据须要被发送,设置正确的头 if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { jqXHR.setRequestHeader( "Content-Type", s.contentType ); } // 根据dataType,设置一个Accept头 jqXHR.setRequestHeader( "Accept", s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ? s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : s.accepts[ "*" ] }; // 历遍s.headers,将其内参数设置入请求头 for ( i in s.headers ) { jqXHR.setRequestHeader( i, s.headers[ i ] ); } // 通过beforeSend检查否是须要发送 if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) { // 终止 return jqXHR.abort(); } // 此时abort数函不是取消ajax,而是中断了ajax strAbort = "abort"; // 在jqXHR上定绑胜利、错误、成完回调数函 for ( i in { success: 1, error: 1, complete: 1 } ) { jqXHR[ i ]( s[ i ] ); } // 得到transport transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); // 如果没有,自动终止 if ( !transport ) { done( -1, "No Transport" ); } else { // 否则,设置reayState为1 jqXHR.readyState = 1; // 如果须要,对特定对象发触全局事件ajaxSend if ( fireGlobals ) { globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); } // 如果是异步,并且设置了超时 if ( s.async && s.timeout > 0 ) { // 设置超时 timeoutTimer = setTimeout(function() { jqXHR.abort("timeout"); }, s.timeout ); } try { // 设置state为1 state = 1; // 开始发送 transport.send( requestHeaders, done ); } catch ( e ) { // 截获错误,如果ajax未成完 if ( state < 2 ) { done( -1, e ); // 成完了就直接抛出错误 } else { throw e; } } } // 成完时的回调数函 function done( status, nativeStatusText, responses, headers ) { var isSuccess, success, error, response, modified, statusText = nativeStatusText; // 如果经已调用过该数函,直接退出 if ( state === 2 ) { return; } // 设置在现状态已成完 state = 2; // 清除超时设置 if ( timeoutTimer ) { clearTimeout( timeoutTimer ); } // 不管jqXHR对象要被用到何时, // 释放transport的引用使得他可以先被垃圾回收 transport = undefined; // 存缓响应头 responseHeadersString = headers || ""; // 设置readyState jqXHR.readyState = status > 0 ? 4 : 0; // 得到响应数据 if ( responses ) { // 通过ajaxHandleResponses处置数据 response = ajaxHandleResponses( s, jqXHR, responses ); } // If successful, handle type chaining // 如果胜利 if ( status >= 200 && status < 300 || status === 304 ) { // 看看否是须要存缓If-Modified-Since和If-None-Match头 if ( s.ifModified ) { // 取得Last-Modified modified = jqXHR.getResponseHeader("Last-Modified"); // 如果Last-Modified存在 if ( modified ) { // 在jQuery.lastModified[cacheURL]保存Last-Modified jQuery.lastModified[ cacheURL ] = modified; } // 取得etag modified = jqXHR.getResponseHeader("etag"); // 如果etag存在 if ( modified ) { // 在jQuery.etag[cacheURL]存缓etag jQuery.etag[ cacheURL ] = modified; } } // 如果没有修改 if ( status === 304 ) { // 设置胜利 isSuccess = true; // 设置状态为notmodified statusText = "notmodified"; // 否则得到必要的数据 } else { isSuccess = ajaxConvert( s, response ); statusText = isSuccess.state; success = isSuccess.data; error = isSuccess.error; isSuccess = !error; } // 如果失败 } else { // 从statusText获取error状态 // 在设置statusText成"error" // 并转变status为0 error = statusText; if ( status || !statusText ) { statusText = "error"; if ( status < 0 ) { status = 0; } } } // 开始设置寨山xhr对象jqXHR的status和statusText jqXHR.status = status; jqXHR.statusText = ( nativeStatusText || statusText ) + ""; // 根据胜利还是失败,对deferred行进回调 if ( isSuccess ) { deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); } else { deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); } // 根据目前statusCode回调 jqXHR.statusCode( statusCode ); statusCode = undefined; // 如果须要发触全局事件 if ( fireGlobals ) { // 对指定元素发触事件ajaxSuccess或者ajaxError globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", [ jqXHR, s, isSuccess ? success : error ] ); } // 回调成完后的Callbacks队列 completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); // 如果须要发触全局事件 if ( fireGlobals ) { // 对指定元素发触事件ajaxComplete globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); // 该ajax发触终了,标记active减1,如果为0,证明有所ajax束结 if ( !( --jQuery.active ) ) { // 发触ajaxStop事件 jQuery.event.trigger("ajaxStop"); } } } // 回返寨山xhr return jqXHR; };
文章结束给大家分享下程序员的一些笑话语录: 手机终究会变成PC,所以ip会比wm更加畅销,但是有一天手机强大到一定程度了就会发现只有wm的支持才能完美享受。就好比树和草,草长得再高也是草,时间到了条件成熟了树就会窜天高了。www.ishuo.cn