问题1:回调函数的上下文是什么?
s = jQuery.ajaxSetup( {}, options ), // Callbacks context //回调函数上下文,默认是自己指定的context,如果没有自己指定context那么就是总的options对象! //在ajaxSetup中的逻辑分为: //第一步url,context直接封装到target上面(因为URL和context都是字符串,不会深度克隆),其它首先封装到deep上面(如xhrFields,headers要深度克隆) //第二步:把deep上面的属性通过调用$.extend方法来封装到target上面! //第三步:返回target对象,该target含有jQuery.ajaxSettings和用户自己指定的options所有的属性! callbackContext = s.context || s;上面的callbackContext就是回调上面文中的this,默认是用户自己指定的context,如果用户没有指定context, 那么就是包括了ajaxSettings和用户自己指定的options所有的属性的对象!
if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) { // Abort if not done already and return return jqXHR.abort(); }note:beforesend中的上下文就是用户指定的context或者总的options对象,如果在beforesend中返回false那么表示提前取消ajax请求.
if ( isSuccess ) { deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); } else { deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); }note:在成功或者失败回调中的this就是用户context或者总的options对象!
completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );note:用处1,2,3都是用于表示回调函数上下文的,如complete回调,success回调,onerror回调,beforesend回调!
//全局事件的上下文的this指定: //如果用户指定了就是用户的context,同时该context是DOM或者jquery对象,那么就把这个this封装为jQuery对象,否则就是jQuery.event对象! // Context for global events is callbackContext if it is a DOM node or jQuery collection globalEventContext = s.context && ( callbackContext.nodeType || callbackContext.jquery ) ? jQuery( callbackContext ) :jQuery.eventnote:如果用户指定了上下文,同时是DOM或者jQuery对象,那么封装为jQuery对象,否则就是jQuery.event对象
用处1:触发ajaxSend方法
globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );//jQuery对象触发或者jQuery.event触发用处2:触发ajaxSuccess/ajaxError方法
globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",[ jqXHR, s, isSuccess ? success : error ] );用处3:触发ajaxCompelte方法
globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );note: 我们必须要明白这些全局事件默认是触发的
fireGlobals = jQuery.event && s.global;//从ajaxSettings中可以看出,默认是响应全局事件的,因为gloabl是true,表示全局事件都会调用 ajaxSettings: { url: ajaxLocation, type: "GET", isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ), global: true, processData: true, async: true, contentType: "application/x-www-form-urlencoded; charset=UTF-8"}问题3:当我们实现跨域请求的时候是否必须手动指定crossDomain参数?
var rurl = /^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/, try { ajaxLocation = location.href;//当前的URL } catch( e ) { // Use the href attribute of an A element // since IE will modify it given document.location ajaxLocation = document.createElement( "a" ); ajaxLocation.href = ""; ajaxLocation = ajaxLocation.href; } // Segment location into parts ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];//当前URL数组,包括协议名,端口号,域名 // A cross-domain request is in order when we have a protocol:host:port mismatch if ( s.crossDomain == null ) { parts = rurl.exec( s.url.toLowerCase() );//访问的URL构成的数组 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" ) ) ) ); }note:在ajax请求中,如果跨域我们没有指定crossDomain参数,那么ajax方法在底层会通过对比协议名,域名,端口号来决定是否是跨域请求, 如果跨域那么自动添加crossDomain为true!
ajax在成功回调的done方法中有这么一段逻辑:
if ( isSuccess ) { // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. if ( s.ifModified ) { modified = jqXHR.getResponseHeader("Last-Modified"); if ( modified ) { jQuery.lastModified[ cacheURL ] = modified; } modified = jqXHR.getResponseHeader("etag"); if ( modified ) { jQuery.etag[ cacheURL ] = modified; } } }note:成功回调之后,如果发现用户指定了isModified,表示用户希望数据没有修改的时候读取缓存。这时候把服务器端返回的Last-Modifed头信息保存到jQuery.lastModified对象中(该对象是一个空对象{}),键名就是上一次访问的URL!同时也把服务器端返回的etag保存到jQuery.etag对象中!那么下次如何使用呢?
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 ] ); } }note:这表明,在发送ajax请求之前,我们判断用户是否指定了ifModified,如果指定了那么我们把上次保存的 Last-Modified通过if-Modified-Since,etag通过if-None-Match发送到服务器端验证数据是否已经修改了!
var rnoContent = /^(?:GET|HEAD)$/, // Determine if request has content s.hasContent = !rnoContent.test( s.type );//get/head请求 if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { jqXHR.setRequestHeader( "Content-Type", s.contentType ); }note:get/head请求如果指定了contentType也会按照contentType的方式对数据进行编码,如果没有指定content-Type那么默认按照 application/x-www-form-urlencoded; charset=UTF-8方式编码。 对于post请求等其它的请求,即使指定了content-type也会按照application/x-www-form-urlencoded; charset=UTF-8方式编码。
(1)通过上面的ajaxSettings对象我们知道ajax默认是异步的请求,也就是aync是true!
(2)异步请求的时候我们可以设置timeout,当超过一定的时间后我们就会取消ajax请求
if ( s.async && s.timeout > 0 ) { timeoutTimer = setTimeout(function() { jqXHR.abort("timeout");//异步的情况下我们超时调用 }, s.timeout ); }(3)如果是同步的请求呢,请看 ajaxTransport分析
if ( !options.async ) { // if we're in sync mode we fire the callback callback(); } else if ( xhr.readyState === 4 ) { // (IE6 & IE7) if it's in cache and has been // retrieved directly we need to fire the callback setTimeout( callback );//IE6/7如果在缓存中,我们必须手动调用! } else { // Add to the list of active xhr callbacks xhr.onreadystatechange = xhrCallbacks[ id ] = callback; }note:如果是同步请求, 那么我们直接回调函数callback,否则我们通过原生的XHR对象,并且把回调绑定到onreadystatechange事件上,判断readystate来完成!这就是同步和异步的差异!同步的时候open的第三个参数是false,这时候我们的javascript不会继续执行,而会等待响应返回,但是对于原生的ajx请求来说onreadystatechange早于open方法!
问题7:jqXHR对象和xhr对象的关系?
// Functions to create xhrs function createStandardXHR() { try { return new window.XMLHttpRequest(); } catch( e ) {} } function createActiveXHR() { try { return new window.ActiveXObject( "Microsoft.XMLHTTP" ); } catch( e ) {} } // Create the request object // (This is still attached to ajaxSettings for backward compatibility) jQuery.ajaxSettings.xhr = window.ActiveXObject !== undefined ? // Support: IE6+ function() { // XHR cannot access local files, always use ActiveX for that case return !this.isLocal && // Support: IE7-8 // oldIE XHR does not support non-RFC2616 methods (#13240) // See http://msdn.microsoft.com/en-us/library/ie/ms536648(v=vs.85).aspx // and http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9 // Although this check for six methods instead of eight // since IE also does not support "trace" and "connect" /^(get|post|head|put|delete|options)$/i.test( this.type ) && createStandardXHR() || createActiveXHR(); } : // For all other browsers, use the standard XMLHttpRequest object createStandardXHR;note:从上面的代码可以知道ajaxSettings对象有一个xhr方法,通过这个方法可以获取到标准的xhr对象。真正的发送ajax请求是通过xhr对象来完成的,而不是通过jqXHR对象来完成的!我们来看看jqXHR对象的代码:
jqXHR = { readyState: 0, // Builds headers hashtable if needed getResponseHeader: function( key ) { var match; if ( state === 2 ) { if ( !responseHeaders ) { responseHeaders = {}; while ( (match = rheaders.exec( responseHeadersString )) ) { responseHeaders[ match[1].toLowerCase() ] = match[ 2 ]; } } match = responseHeaders[ key.toLowerCase() ]; } return match == null ? null : match; }, // Raw string getAllResponseHeaders: function() { return state === 2 ? responseHeadersString : null; }, // Caches the header setRequestHeader: function( name, value ) { var lname = name.toLowerCase(); if ( !state ) {//requestHeadersNames保存的是所有的已经设置的HTTP头,requestHeaders保存的键值对! name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name; requestHeaders[ name ] = value;//name是HTTP头的名称,而value是HTTP的值! } return this; }, // Overrides response content-type header overrideMimeType: function( type ) { if ( !state ) { s.mimeType = type; } return this; }, // Status-dependent callbacks statusCode: function( map ) { var code; if ( map ) { if ( state < 2 ) { for ( code in map ) { // Lazy-add the new callback in a way that preserves old ones statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; } } else { // Execute the appropriate callbacks jqXHR.always( map[ jqXHR.status ] ); } } return this; }, // Cancel the request abort: function( statusText ) { var finalText = statusText || strAbort; if ( transport ) { transport.abort( finalText ); } done( 0, finalText ); return this; } }; // Attach deferreds deferred.promise( jqXHR ).complete = completeDeferred.add; jqXHR.success = jqXHR.done; jqXHR.error = jqXHR.fail;note:从这里我们可以看到jqXHR对象和原生的xhr对象没有任何关系,那么为什么会把所有的头信息都封装到jqXHR对象上面呢,如下:
for ( i in s.headers ) { jqXHR.setRequestHeader( i, s.headers[ i ] ); }其实,这是有用的,因为jqXHR的setRequestHeader把所有的信息保存到requestHeaders集合中, 最后通过如下代码传入真正的xhr作为头信息发送:
transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); transport.send( requestHeaders, done );//transport返回的是对象有send方法,该方法第一个参数就是HTTP头信息!
我们来看看最终是如何通过XHR对象把HTTP头集合发送出去的,下面这个ajaxTransport对象是除了script类型外的通用的发送ajax请求的逻辑
// Determine support properties support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); xhrSupported = support.ajax = !!xhrSupported; // Create transport if the browser can provide an xhr if ( xhrSupported ) { jQuery.ajaxTransport(function( options ) { // Cross domain only allowed if supported through XMLHttpRequest if ( !options.crossDomain || support.cors ) { var callback; return { //接受HTTP头信息数组和回调函数! send: function( headers, complete ) { var i, xhr = options.xhr(),//xhr被封装到最终的options对象上面 id = ++xhrId; // Open the socket xhr.open( options.type, options.url, options.async, options.username, options.password ); // Apply custom fields if provided if ( options.xhrFields ) { for ( i in options.xhrFields ) { xhr[ i ] = options.xhrFields[ i ]; } } // Override mime type if needed if ( options.mimeType && xhr.overrideMimeType ) { xhr.overrideMimeType( options.mimeType ); } // X-Requested-With header // For cross-domain requests, seeing as conditions for a preflight are // akin to a jigsaw puzzle, we simply never set it to be sure. // (it can always be set on a per-request basis or even using ajaxSetup) // For same-domain requests, won't change header if already provided. if ( !options.crossDomain && !headers["X-Requested-With"] ) { headers["X-Requested-With"] = "XMLHttpRequest"; } // Set headers for ( i in headers ) { // Support: IE<9 // IE's ActiveXObject throws a 'Type Mismatch' exception when setting // request header to a null-value. // // To keep consistent with other XHR implementations, cast the value // to string and ignore `undefined`. if ( headers[ i ] !== undefined ) { xhr.setRequestHeader( i, headers[ i ] + "" ); } } // Do send the request // This may raise an exception which is actually // handled in jQuery.ajax (so no try/catch here) xhr.send( ( options.hasContent && options.data ) || null ); // Listener callback = function( _, isAbort ) {//该对象有send方法和abort方法,如果调用了abort方法那么isAbort就是true! var status, statusText, responses; // Was never called and is aborted or complete if ( callback && ( isAbort || xhr.readyState === 4 ) ) {//没有调用过,同时已经取消了或者完成 // Clean up delete xhrCallbacks[ id ]; callback = undefined;//回调函数清空 xhr.onreadystatechange = jQuery.noop;//onreadystate清空 // Abort manually if needed if ( isAbort ) {//如果手动取消那么结束ajax请求! if ( xhr.readyState !== 4 ) { xhr.abort(); } } else { responses = {}; status = xhr.status; // Support: IE<10 // Accessing binary-data responseText throws an exception // (#11426) if ( typeof xhr.responseText === "string" ) { responses.text = xhr.responseText; } // Firefox throws an exception when accessing // statusText for faulty cross-domain requests try { statusText = xhr.statusText; } catch( e ) { // We normalize with Webkit giving an empty statusText statusText = ""; } // Filter status for non standard behaviors // If the request is local and we have data: assume a success // (success with no data won't get notified, that's the best we // can do given current implementations) if ( !status && options.isLocal && !options.crossDomain ) { status = responses.text ? 200 : 404; // IE - #1450: sometimes returns 1223 when it should be 204 } else if ( status === 1223 ) { status = 204; } } } // Call complete if needed if ( responses ) { complete( status, statusText, responses, xhr.getAllResponseHeaders() ); } }; if ( !options.async ) { // if we're in sync mode we fire the callback callback(); } else if ( xhr.readyState === 4 ) { // (IE6 & IE7) if it's in cache and has been // retrieved directly we need to fire the callback setTimeout( callback ); } else { // Add to the list of active xhr callbacks xhr.onreadystatechange = xhrCallbacks[ id ] = callback; } }, abort: function() { if ( callback ) { callback( undefined, true ); } } }; } });note:看到里面的send方法了把,他接受的就是jqXHR对象上面封装的所有的HTTP头信息。通过这里代码,你要弄明白以下问题:
(1)open方法前三个参数容易理解,后两个参数username表示用于响应HTTP访问认证请求的用户名;password表示用于响应HTTP访问认证请求的密码。
(2)jqXHR的overrideMimeType只是用来修改最终options中的mimeType,而真正对mimeType的修改是在xhr对象上面,他会读取最终options对象的mimeType值!
(3)XHR中自定义的HTTP头必须在open方法调用后,在send方法调用之前发送,同时对于overrideMimeType调用也要在open后send之前调用,才能保证重写响应的MIME类型!
(4)关于xhr请求的特点的HTTP头
if ( !options.crossDomain && !headers["X-Requested-With"] ) { headers["X-Requested-With"] = "XMLHttpRequest"; }note:这表示如果是跨域的请求(那么没有自己指定crossDomain,jquery会自动检测), 那么我们不会发送X-requested-with;如果不是跨域的请求,那么该HTTP头总会被发送!
(6)有浏览器会错误的报告204状态码,IE中XHR的ActiveX版本会把204设置为1223,而IE中原生的XHR则会将204规范化为200,opera会在取得204时候status为0。
if ( !status && options.isLocal && !options.crossDomain ) { status = responses.text ? 200 : 404;//如果状态吗回报为0,同时也是本地请求不跨域,如果有返回那么就是200,否则就是404 // IE - #1450: sometimes returns 1223 when it should be 204 } else if ( status === 1223 ) { status = 204;//IE回报204为1123 }note: 如果是本地非跨域请求,但是返回的状态码为0,那么如果有返回值那么就是200,否则就是404!
abort: function( statusText ) { var finalText = statusText || strAbort;//strAbort默认是"cancelled" if ( transport ) { transport.abort( finalText );//调用xhr的abort方法 } done( 0, finalText );//取消了以后还是要进行回调 return this; }note:如果调用jqXHR的abort方法,那么我们还是通过调用相应的ajaxTransport方法来取消,我们看看通用的ajaxTransport是如何处理的
abort: function() { if ( callback ) { callback( undefined, true ); } }note:jqXHR的abort方法最终还是调用了上面的callback方法的abort方法, 不过这次调用的isAbort为true!最终执行xhr的abort方法取消ajax请求。但是jqXHR调用abort后还会执行函数 done方法,所以最后执行也表示成功执行ajax请求了!
(8)上面说了通用的ajaxTransport,那么还有一个特备的针对script进行处理的ajax请求方式
// Bind script tag hack transport jQuery.ajaxTransport( "script", function(s) { // This transport only deals with cross domain requests if ( s.crossDomain ) { var script, head = document.head || jQuery("head")[0] || document.documentElement; return { send: function( _, callback ) { //通过动态创建script来完成,同时是异步的,而且编码就是自己在options中指定的scriptCharset! script = document.createElement("script"); script.async = true; if ( s.scriptCharset ) { script.charset = s.scriptCharset; } //指定script的src和回调之间没有先后关系限制,如果是图片必须首先指定回调才行! script.src = s.url; // Attach handlers for all browsers script.onload = script.onreadystatechange = function( _, isAbort ) { if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) { //如果已经完成请求,那么把onload和onreadystatechange清空,防止内存泄漏! // Handle memory leak in IE script.onload = script.onreadystatechange = null; //移除这个脚本 // Remove the script if ( script.parentNode ) { script.parentNode.removeChild( script ); } // Dereference the script script = null; //加载完成进行回调! // Callback if not abort if ( !isAbort ) { callback( 200, "success" ); } } }; // Circumvent IE6 bugs with base elements (#2709 and #4378) by prepending // Use native DOM manipulation to avoid our domManip AJAX trickery //把scipt元素添加到head最前面的! head.insertBefore( script, head.firstChild ); }, abort: function() { if ( script ) { script.onload( undefined, true );//这时候就不会回调callback函数,因为isAbort为true! } } }; } });
note:这个ajaxTransport是专门处理script标签跨域的问题,如果不是跨域,我们还是通过上面提到的通用的ajaxTransport,我们需要注意以下几点:
(1)跨域脚本是异步的,同时为了防止IE的内存泄漏,我们在onload和onreadystatechange中做了处理,如果是IE还是非IE浏览器都会移除引用
(2)加载成功也会回调我们源码中的done方法,其实现跨域脚本的加载原理很简单,通过动态script来完成!
setRequestHeader: function( name, value ) { var lname = name.toLowerCase(); if ( !state ) { name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name; requestHeaders[ name ] = value; } return this; }state是0的时候也可以修改返回的内容的mimeType类型
overrideMimeType: function( type ) { if ( !state ) { s.mimeType = type; } return this; }state为1,表示我们还【 没有调用open方法,send方法】
try { state = 1; transport.send( requestHeaders, done );//调用send和open方法的逻辑,之前state为1 } catch ( e ) { // Propagate exception as error if not done if ( state < 2 ) {//如果send时候抛出异常,我们手动回调done方法,传入参数为-1 done( -1, e ); // Simply rethrow otherwise } else { throw e; } }state为2,【 也就是open,send已经完成了,已经到了客户端回调】
问题9:open,send方法和ajaxStart等的调用顺序?
beforeSend最先调用,上线文为最终的callbackContext对象,第一个参数也是jqXHR,第二个参数是最终options对象
(1)调用ajaxStart方法
(2)ajaxSend方法调用,参数为jqXHR和最终options
(3)调用open/send方法,完成ajax发送
(4)调用ajaxSuccess/ajaxError方法,参数为jqXHR对象,最终options对象,如果成功那么第三个参数是response.data,如果失败那么第三个参数是response.error!
(5)调用ajaxComplete方法
(6)调用ajaxStop方法
callbackContext = s.context || s, // Context for global events is callbackContext if it is a DOM node or jQuery collection globalEventContext = s.context && ( callbackContext.nodeType || callbackContext.jquery ) ?jQuery( callbackContext ) :jQuery.event jQuery.event.trigger("ajaxStart");//通过ajaxStart(function(){})这种类型来绑定 globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",[ jqXHR, s, isSuccess ? success : error ] ); globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); jQuery.event.trigger("ajaxStop");//和ajaxStart绑定的方式相同,他是通过jQuery.event.trigger来触发!
note:如果指定了context,同时context是DOM类型或者jQuery类型,那么globalEventContex就是该DOM的jQuery对象;否则为jQuery.event对象!在除了ajaxStart,ajaxStop类型的方式以外,其它的方法的第一个参数是jqXHR,第二个参数是最终options对象!所以下面这些方法都是可以有参数的:
window.onload=function() { //在调用send/open方法之后 $(document).ajaxError(function(jqXHR,options,error) { console.log(options); console.log(error); }); //这是在调用open/send之前的方法 $(document).ajaxSend(function(jqXHR,options) { }); $.ajax({ url:"http://localhost:8080/qinl", dataType:"json", context:$("#q")[0]//指定context }); }
问题10:jqXHR对象的jqXHR.readyState如何变化?
默认状态是0:
jqXHR = { readyState: 0//默认状态是0 }在调用send/open方法之前的状态为1:
if ( !transport ) { done( -1, "No Transport" ); } else { jqXHR.readyState = 1;//在open/send方法调用之前状态为1! // Send global event if ( fireGlobals ) { globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); } // Timeout if ( s.async && s.timeout > 0 ) { timeoutTimer = setTimeout(function() { jqXHR.abort("timeout"); }, s.timeout ); } try { state = 1; transport.send( requestHeaders, done ); } catch ( e ) { // Propagate exception as error if not done if ( state < 2 ) { done( -1, e ); // Simply rethrow otherwise } else { throw e; } } }在done回调函数中对它重新设置,成功为4,失败为0:
//取消的时候status为0,第二个参数是"cancled" //如果没有找到transport那么status为-1,第二个参数是"No transport" //如果调用ajaxTransport出错的时候,第一个参数是-1,第二个参数是异常本身e! //如果是在ajaxTransport对象的send方法中调用:complete( status, statusText, responses, xhr.getAllResponseHeaders() ); //那么这时候status就是status = xhr.status; jqXHR.readyState = status > 0 ? 4 : 0;note:如果是取消请求,或者没有找到ajaxTransport,或者是调用send方法出错那么状态回到0,如果请求成功那么状态变为4!
问题11:statusCode是如何起作用的?
var statusCode = s.statusCode || {},//初始statusCode! // Status-dependent callbacks statusCode: function( map ) { var code; if ( map ) { if ( state < 2 ) {//state<2表示还没有调用open/send方法,这时候在保存原来的相应状态码回调函数的基础上把新的相应的状态码添加进去 for ( code in map ) { // Lazy-add the new callback in a way that preserves old ones statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; } } else { // Execute the appropriate callbacks jqXHR.always( map[ jqXHR.status ] );//这时候把相应的回调函数添加到always回调中,也就是不管成功或者失败都是会调用的! } } return this; } jqXHR.statusCode( statusCode );//done回调中使用,也就是说在ajax到了客户端回调时候才开始操作statusCode!通过statusCode:{204:func,304:func}指定!
问题12:可以直接为jqXHR指定error,complete,success方法,那么他们的回调参数有区别吗?
deferred.promise( jqXHR ).complete = completeDeferred.add; jqXHR.success = jqXHR.done; jqXHR.error = jqXHR.fail;
都是通过把这写函数添加到jqXHR["success"],jqXHR["error"],jqXHR["complete"]中,所以成功直接resolve,失败直接rejectWith,调用方式如下:
if ( isSuccess ) { deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );//上下文为callbackContext } else { deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); }note:成功回调第一个参数是返回的经过ajaxConverter处理的数据,第二个参数是状态信息,第三个参数是jqXHR!如果是失败回调那么第一个参数是jqXHR,第二个参数是状态信息,第三个是error信息!
completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );//第一个参数是jqXHR,第二个参数是statusText!
note:知道这个原理以后我们就可以通过done,fail或者complete来添加回调函数了,都是可以执行的!
问题13:为什么需要jqXHR?
我们首先需要分析ajax最终的回调函数
// Callback for when everything is done function done( status, nativeStatusText, responses, headers ) { var isSuccess, success, error, response, modified, //保存如"canceled",""No Transport""等字符串 statusText = nativeStatusText; //state为2表示调用过一次了,因为state=2只有在done函数中设置 // Called once if ( state === 2 ) { return; } // State is "done" now state = 2; // Clear timeout if it exists //异步调用的时候如果时间到还没有触发,那么我们清除这个定时器 if ( timeoutTimer ) { clearTimeout( timeoutTimer ); } // Dereference transport for early garbage collection // (no matter how long the jqXHR object will be used) transport = undefined; // Cache response headers //用responseHeadersString保存传递给done方法的headers参数 responseHeadersString = headers || ""; // Set readyState //取消的时候status为0,第二个参数是"cancled" //如果没有找到transport那么status为-1,第二个参数是"No transport" //如果调用ajaxTransport出错的时候,第一个参数是-1,第二个参数是异常本身e! //如果是在ajaxTransport对象的send方法中调用:complete( status, statusText, responses, xhr.getAllResponseHeaders() ); //那么这时候status就是status = xhr.status; jqXHR.readyState = status > 0 ? 4 : 0; // Determine if successful isSuccess = status >= 200 && status < 300 || status === 304; // Get response data //对数据进行处理,把我们设置为"json"变成"text json"保存到dataTypes数组中用于后面的ajaxConvert把它转化为json数据! //返回的数据就是服务器端返回的responseText! if ( responses ) { response = ajaxHandleResponses( s, jqXHR, responses ); } //通过类型转化了,转化为我们通过dataType指定的类型了! // Convert no matter what (that way responseXXX fields are always set) response = ajaxConvert( s, response, jqXHR, isSuccess ); // If successful, handle type chaining if ( isSuccess ) { // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. //设置缓存HTTP头 if ( s.ifModified ) { modified = jqXHR.getResponseHeader("Last-Modified"); if ( modified ) { jQuery.lastModified[ cacheURL ] = modified; } modified = jqXHR.getResponseHeader("etag"); if ( modified ) { jQuery.etag[ cacheURL ] = modified; } } //204表示没有数据返回,或者HEAD请求也是没有数据返回的,这时候statusText就是"nocontent"! // if no content if ( status === 204 || s.type === "HEAD" ) { statusText = "nocontent"; // if not modified } else if ( status === 304 ) { statusText = "notmodified"; // If we have data, let's convert it } else { //其中state就是parsererror或者success等字符串 //data就是转换陈功的数据!error就是异常的时候信息,如果成功response.error是false,于是isSuccess就是true! statusText = response.state; success = response.data; error = response.error; isSuccess = !error; } } else {//如果失败了! // We extract error from statusText // then normalize statusText and status for non-aborts //error就是如"canceled",""No Transport""等字符串 error = statusText; //如果是取消(取消status为0)或者没有statusText! if ( status || !statusText ) { statusText = "error"; //如果找到ajaxTransport或者调用transport.send报错,那么状态就是0! if ( status < 0 ) { status = 0; } } } // Set data for the fake xhr object jqXHR.status = status; jqXHR.statusText = ( nativeStatusText || statusText ) + ""; //如果成功那么statusText可能为nocontent,notmodified等! //如果失败那么就是"error"字符串 // Success/Error if ( isSuccess ) { deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); } else { deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); } //把相应的状态码的回调函数添加到always中等待执行! // Status-dependent callbacks jqXHR.statusCode( statusCode ); statusCode = undefined; //每次执行全局事件之前都经过了同样的判断! if ( fireGlobals ) { globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", [ jqXHR, s, isSuccess ? success : error ] ); } // Complete completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); if ( fireGlobals ) { globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); // Handle the global AJAX counter if ( !( --jQuery.active ) ) { jQuery.event.trigger("ajaxStop"); } } } return jqXHR; }note:其实引入jqXHR对象我觉得是为了让逻辑更加清晰,最后的ajax的调用还是通过xhr对象来完成的!但是我们通过把error,complete,success封装到jqXHR对象上可以有效的控制ajax完成或者失败的回调,而 不用通过xhr去逐个添加回调函数!分析了源码以后我们甚至可以通过 Deferred对象的fail,done,always等方式
getResponseHeader: function( key ) { var match; if ( state === 2 ) { if ( !responseHeaders ) { responseHeaders = {}; // IE leaves an \r character at EOL //var rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg //responseHeadersString = headers || "";这是在done方法中的逻辑 //send方法中逻辑,complete( status, statusText, responses, xhr.getAllResponseHeaders() ); while ( (match = rheaders.exec( responseHeadersString )) ) { //第一个捕获组是头的名,第二个是HTTP头的值 responseHeaders[ match[1].toLowerCase() ] = match[ 2 ]; } } match = responseHeaders[ key.toLowerCase() ]; } return match == null ? null : match; }note:其实是通过xhr.getAllResponseHeaders获取到所有返回的HTTP头,然后用正则表达式去匹配特定的内容!
if ( s.data && s.processData && typeof s.data !== "string" ) { s.data = jQuery.param( s.data, s.traditional ); }
note:我们很容易看到,如果含有数据,同时processData为true(默认为true,默认情况下,通过data属性传递进来的数据,如果是一个对象(技术上讲,只要不是字符串),都会处理转化成一个查询字符串,以配合默认内容类型 "application/x-www-form-urlencoded"。如果要发送 DOM树信息或其它不希望转换的信息,请设置为false),同时,满足数据不是string类型,那么就会通过jQuery.param来处理,该函数内部调用encodeURIComponent来对请求参数名和参数值进行编码
总结:
上面的请求都不牵涉CORS,如果牵涉跨域,这时候就需要用到CORS相关知识。
(1)IE使用XDR来完成,而且这种机制只能实现异步请求,无法实现同步;他也不发送cookie同时也不接受cookie;不能访问相应头信息,只能支持GET/POST请求;只能设置请求头Content-Type。
(2)其它浏览器实现了对CORS的原生支持,在尝试打开不同的源的资源的时候无需添加额外的代码。与IE的XDR相比,通过跨域的XHR可以访问status和statusText属性,而且支持同步;不能通过setRequestHeader设置自定义头;不能发送和接受cookie;调用getAllResponseHeaders总会返回空字符串