jQuery-1.9.1源码分析系列(十六)ajax——ajax处理流程以及核心函数

王朝学院·作者佚名  2016-08-27
窄屏简体版  字體: 小  |  中  |  大  |  超大  

先来看一看jQuery的Ajax核心处理流程($.ajax)

a. ajax( [url,] options )执行流程第一步,为传递的参数做适配。url可以包含在options中

//传递的参数只是一个对象if(typeofurl === "object") {

options=url;

url=undefined;

}//options强制转成对象options = options || {};

第二步,创建一些变量,比较重要的是:创建最终选项对象s、全局事件上下文是callbackContext、创建deferred和completeDeferred、创建jqXHR对象。

var//跨域检测变量parts,

...//创建最终选项对象s=jQuery.ajaxSetup( {}, options ),//回调上下文callbackContext = s.context ||s,//全局事件上下文是callbackContext,如果他是一个DOM节点或jQuery集合(对象)globalEventContext= s.context && ( callbackContext.nodeType || callbackContext.jquery ) ?jQuery( callbackContext ) :

jQuery.event,//Deferredsdeferred=jQuery.Deferred(),completeDeferred= jQuery.Callbacks("once memory"),

...jqXHR={

readyState:0,//建立请求头哈希表getResponseHeader:function( key ) {...},//Raw stringgetAllResponseHeaders:function() {...},//缓存请求头setRequestHeader:function( name, value ) {...},//重写响应content-type头overrideMimeType:function( type ) {...},//取决于状态的回调statusCode:function( map ) {...},//取消请求abort:function( statusText ) {...}

};//添加延时事件deferred.PRomise( jqXHR ).complete =completeDeferred.add;

jqXHR.success=jqXHR.done;

jqXHR.error= jqXHR.fail;

第三步,检查是否跨域。其中需要注意的是ajaxLocParts在jQuery初始化的时候就定义了

//rurl = /^([\w.+-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)///需要注意的是本地文件一般形如"file:///C:/Users/Administrator/Desktop/jquery/test.html"//最终结果为["file://", "file:", "", undefined]//正常http请求如"http://www.baidu.com"//的到结果为["http://www.baidu.com", "http:", "www.baidu.com", undefined]//如果是"http://192.168.0.17:8080/baidu/com"//则得到的结果["http://192.168.0.17:8080", "http:", "192.168.0.17", "8080"]ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) ||[];//跨域请求是为了当我们有一个协议:host:port不匹配的时候if( s.crossDomain ==null) {

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转化成一个查询字符串

//processData默认为true//默认情况下,通过data属性传递进来的数据,如果是一个对象(技术上讲,只要不是字符串),//都会处理转化成一个查询字符串,以配合默认内容类型 "application/x-www-form-urlencoded"if( s.data && s.processData &&typeofs.data !== "string") {

s.data=jQuery.param( s.data, s.traditional );

}

第五步,运行prefilters进行预处理

//运行prefiltersinspectPrefiltersOrTransports( prefilters, s, options, jqXHR );

预处理和分发器使用的是同一个函数inspectPrefiltersOrTransports,需要注意的是当dataType为jsonp的时候是以dataType为script的方式来处理的

第六步,根据传递的选项设置默认参数处理(主要包括如果type是GET等类型,传递的数据将被附加到URL上;添加请求头如If-Modified-Since/If-None-Match、Content-Type、Accept等;)

…//没有请求内容(type一般为GET的情况)if( !s.hasContent ) {//如果data可用,添加到urlif( s.data ) {

cacheURL= ( s.url += ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) +s.data );//#9682:删除data保证重试是不会被使用deletes.data;

}//cache默认值:true(dataType为'script'或'jsonp'时,则默认为false)。//指示是否缓存URL请求。如果设为false将强制浏览器不缓存当前URL请求。//该参数只对HEAD、GET请求有效(POST请求本身就不会缓存)if( s.cache ===false) {//rts = /([?&])_=[^&]*/s.url = rts.test( cacheURL ) ?//如果已经有一个'_'参数,设置他的值cacheURL.replace( rts, "$1_=" + ajax_nonce++) ://否则添加到url后面//ajax_rquery = /\?/cacheURL + ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ajax_nonce++;

}

}//Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.//ifModified默认为false//允许当前请求仅在服务器数据改变时获取新数据(如未更改,浏览器从缓存中获取数据)//它使用HTTP头信息Last-Modified来判断。从jQuery 1.4开始,他也会检查服务器指定的'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 ] );

}

}//contentType默认值:'application/x-www-form-urlencoded; charset=UTF-8'。//使用指定的内容编码类型将数据发送给服务器。//W3C的xmlHttpRequest规范规定charset始终是UTF-8,将其改也无法强制浏览器更改字符编码。if( s.data && s.hasContent && s.contentType !==false||options.contentType ) {

jqXHR.setRequestHeader("Content-Type", s.contentType );

}//设置Accept头,依赖于dataTypejqXHR.setRequestHeader("Accept",

s.dataTypes[0 ] && s.accepts[ s.dataTypes[0] ] ?s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "") :

s.accepts["*"]

);//Check for headers option//headers默认值:{}。//以对象形式指定附加的请求头信息。请求头X-Requested-With:xmlhttpRequest将始终被添加,//当然你也可以在此处修改默认的XMLHttpRequest值。//headers中的值可以覆盖beforeSend回调函数中设置的请求头(意即beforeSend先被调用)。for( iins.headers ) {

jqXHR.setRequestHeader( i, s.headers[ i ] );

}

…//安装回调到deferreds上for( iin{ success: 1, error: 1, complete: 1} ) {

jqXHR[ i ]( s[ i ] );

}

View Code

第七步,执行请求分发

//执行请求分发transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );

第八步,发送请求,附加上回调处理done,以及异常处理。done函数里面处理ajax请求完成(成功或失败)后的处理。

//如果没有transport,自动中止if( !transport ) {done(-1, "No Transport");

}else{

jqXHR.readyState= 1;//触发ajaxSend事件...//请求允许时长限时处理…//发送ajax请求try{

state= 1;

transport.send( requestHeaders, done );

}catch( e ) {//传播异常的错误,如果没有成功if( state < 2) {done(-1, e );//其他情况简单的处理}else{throwe;

}

}

接下来是请求返回之后的处理,全部在done函数中完成:包括更新一些全局的状态、调用ajaxHandleResponses解析响应数据、针对响应返回的状态码自动判断执行那些Deferred延时以及触发哪些全局事件。把done函数的主要源码贴一下

//ajax完成后的回调functiondone( status, nativeStatusText, responses, headers ) {

...//状态改为"done"state = 2;//清除timeoutif( timeoutTimer ) {

clearTimeout( timeoutTimer );

}

...//获取响应数据if( responses ) {

response=ajaxHandleResponses( s, jqXHR, responses );

}

...//如果成功,处理之if( status >= 200 && status < 300 || status === 304) {//在ifModified模式下设置 If-Modified-Since and/or If-None-Match headerif( s.ifModified ) {...}//没有新文档if( status === 204) {

...//客户端有缓冲的文档并发出了一个条件性的请求//(一般是提供If-Modified-Since头表示客户只想比指定日期更新的文档)。//服务器告诉客户,原来缓冲的文档还可以继续使用。}elseif( status === 304) {

...//如果有数据,我们转换他}else{

isSuccess=ajaxConvert( s, response );

statusText=isSuccess.state;

success=isSuccess.data;

error=isSuccess.error;

isSuccess= !error;

}//如果出错}else{//我们从状态文本提取错误,然后正常化状态文本和状态给没有中止的请求error =statusText;if( status || !statusText ) {

statusText= "error";if( status < 0) {

status= 0;

}

}

}//为jqXHR对象设置数据jqXHR.status =status;

jqXHR.statusText= ( nativeStatusText || statusText ) + "";//Deferred执行和全局事件触发处理...

}

到此,整个流程完结。

b. 预处理prefilters和请求分发trasports结构预处理prefilters和请求分发trasports的初始化都是调用addToPrefiltersOrTransports返回一个包装函数,然后调用这个包装函数给prefilters和transports添加属性。

jQuery.extend({ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),ajaxTransport: addToPrefiltersOrTransports( transports ),

}//jQuery.ajaxPrefilter和jQuery.ajaxTransport基础构造函数构造函数functionaddToPrefiltersOrTransports( structure ) {//dataTypeExpression is optional and defaults to "*"returnfunction( dataTypeExpression, func ) {...};

}

举一个初始化预处理prefilters的例子

jQuery.ajaxPrefilter( "script",function( s ) {if( s.cache ===undefined ) {

s.cache=false;

}if( s.crossDomain ) {

s.type= "GET";

s.global=false;

}

});

其他的初始化都是类似,最终预处理prefilters初始化完成以后的结果是

而分发器初始化完成后的结果是

预处理和分发器使用的是同一个函数inspectPrefiltersOrTransports来触发,需要注意的是当dataType为jsonp的时候是以dataType为script的方式来处理的。

//基本功能用于预处理过滤器和分发器//structure对应的是prefilters和transports/*prefilters = {

script: [function(s){...}],

json: [function(s, originalSettings, jqXHR){...}],

jsonp: [function(s, originalSettings, jqXHR){...}]

}

transports = {

*: [function(s){...}],

script: [function(s){...}]

}*/functioninspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) {varinspected ={},

seekingTransport= ( structure ===transports );functioninspect( dataType ) {varselected;

inspected[ dataType ]=true;//structure[ dataType ]获取置顶的处理函数数组(目前数组长度都是1)jQuery.each( structure[ dataType ] || [],function( _, prefilterOrFactory ) {//执行预处理或分发函数vardataTypeOrTransport =prefilterOrFactory( options, originalOptions, jqXHR );//“jsonp”的预处理进入该分支(dataTypeOrTransport为“script”),jsonp最终以datatype为“script”的方式来处理if(typeofdataTypeOrTransport === "string" && !seekingTransport && !inspected[ dataTypeOrTransport ] ) {

options.dataTypes.unshift( dataTypeOrTransport );//dataTypeOrTransport为"script",执行script的预处理inspect( dataTypeOrTransport );returnfalse;}elseif( seekingTransport ) {return!( selected =dataTypeOrTransport );

}

});returnselected;

}returninspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*");

}

c. 预处理prefilters详细分析预处理有三种:json/jsonp/script

首先我们需要明白为什么需要进行预处理。

在dataType为"script"的情况下,我们需要强制处理缓存的特殊情况;如果是跨域则需要强制类型为GET,并禁止触发全局事件。这个处理的源码如下

jQuery.ajaxPrefilter( "script",function( s ) {if( s.cache ===undefined ) {

s.cache=false;

}//注意:在远程请求时(不在同一个域下),所有POST请求都将转为GET请求。(因为将使用DOM的script标签来加载)if( s.crossDomain ) {

s.type= "GET";

s.global=false;

}

});

在dataType为"json"的情况下实际上是什么都没有做。

另一个需要预处理的是当dataType为jsonp的情况。 jsonp下情况比较特殊,jsonp的原理详见。

jsonp原理页面也有jQuery处理的分析。这里就简单介绍了。处理步骤如下(jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR )的源码)

首先,我们需要设置回调函数名称,可以自己定义也可以让jQuery自动设置。

//获取回调函数名称(这个名称可以在ajax的jsonpCallback选项上设置,//否则通过jQuery默认的方式jsonpCallback()来设置)//这个回调函数名称是用来告诉后台需要将返回数据包裹到该函数中,返回前端后执行callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ?s.jsonpCallback() :

s.jsonpCallback;

然后,将回调插入到url或者data选项中(一般来说是URL)

if( jsonProp ) {

s[ jsonProp ]= s[ jsonProp ].replace( rjsonp, "$1" +callbackName );

}elseif( s.jsonp !==false) {

s.url+= ( ajax_rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" +callbackName;

}

再然后,安装回调。

//安装回调overwritten =window[ callbackName ];

window[ callbackName ]=function() {

responseContainer=arguments;

};

很明显,本来后台应该执行overwritten这个回调的,但是现在换成了执行后面重写的这个回调。别急overwritten后面有掉用到。

在安装回调之前还有一个步骤:添加"script json"转换器

//使用数据转换器,脚本执行后取回JSONs.converters["script json"] =function() {if( !responseContainer ) {

jQuery.error( callbackName+ " was not called");

}returnresponseContainer[ 0];

};

我们可能疑惑responseContainer是怎么来的?看到了安装回调了么,responseContainer就是后台调用了window[ callbackName ]这个回调以后获取到的。

最后,添加延时对象的always响应执行overwritten。

//清除函数(转换完成后执行)jqXHR.always(function() {//保存先前存的值window[ callbackName ] =overwritten;//将jsonpCallback设置回原始值if( s[ callbackName ] ) {//确保重新使用jsonpCallback选项没有杂质s.jsonpCallback =originalSettings.jsonpCallback;//将callbackName压入oldCallbacks以备将来使用oldCallbacks.push( callbackName );

}//在请求响应后,如果jsonpCallback指定的回调是一个函数则调用它if( responseContainer &&jQuery.isFunction( overwritten ) ) {

overwritten( responseContainer[0] );

}

responseContainer= overwritten =undefined;

});

所以overwritten是在这里面执行的,前面源码中重载过的那个回调在后台调用后获取到了responseContainer值也被用到了。可见我们在设置的jsonpCallback选项指定的回调名(例如chuaRemote)对应的回调先被保存到overwritten中,而这个原始的chuaRemote被赋值为function() { responseContainer = arguments;};

在响应处理中chuaRemote会被调用让responseContainer获取到响应值。最后会执行到jqXHR.always添加的函数处理。将chuaRemote恢复到原来的函数overwritten,并执行overwritten(jsonpCallback指定的回调)。主要是always这个监听处理中还清除callbackName指定的函数,以及添加回到历史等等处理。

d. 分发器ajaxTransport分发器干啥的?前面不是说了jQuery的ajax处理方式有两种么,一种直接使用浏览器的ajax接口处理,另一种是使用script的src来处理。分发器就是将这两中情况分发给他们两者的专用处理器来处理。

分发器ajaxTransport在jQuery初始化完成后得到了分发处理的两种类型

两种类型中除开跨域使用script指定的方式外,都使用*指定的方式。

我们先看一下script方式的分发器如下

jQuery.ajaxTransport( "script",function(s) {//仅用于跨域if( s.crossDomain ) {varscript,

head= document.head || jQuery("head")[0] ||document.documentElement;return{

send:function( _, callback ) {

script= document.createElement("script");

script.async=true;if( s.scriptCharset ) {

script.charset=s.scriptCharset;

}

script.src=s.url;//添加事件处理script.onload = script.onreadystatechange =function( _, isAbort ) {…};//使用本地DOM操作,以避免我们的domManip AJAX挂羊头卖狗肉head.insertBefore( script, head.firstChild );

},

abort:function() {if( script ) {

script.onload( undefined,true);

}

}

};

}

});

可见跨域请求使用动态加载script标签的方式来完成,所有的参数都附加到url上。dataType为jsonp也是使用该方式。

需要注意一点的是判断script标签加载完成的回调处理

script.onload = script.onreadystatechange =function( _, isAbort ) {//这种写法的取巧之处在于onload和onreadystatechage都用同一个函数,//Firefox/Safari/Chrome/Opera中不支持onreadystatechage事件,也没有readyState属性,//所以 !this.readyState 是针对这些浏览器。readyState是针对IE浏览器,载入完毕的情况是loaded,//缓存的情况下可能会出现readyState为complete。所以两个不能少。//但由于IE9/10也已经支持onload事件了,会造成callback执行2次。//所以执行一次以后设置了script.onload = script.onreadystatechange = null;if( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) {//处理IE内存script.onload = script.onreadystatechange =null;//移除script节点if( script.parentNode ) {

script.parentNode.removeChild( script );

}//script内存清空script =null;//Callback if not abortif( !isAbort ) {

callback(200, "success");

}

}

};

接下来我们看一下普通类型的ajax请求分发是如何处理的

jQuery.ajaxTransport(function( s ) {//Cross domain only allowed if supported through XMLHttpRequestif( !s.crossDomain ||jQuery.support.cors ) {varcallback;return{

send:function( headers, complete ) {//Get a new xhrvarhandle, i,

xhr=s.xhr();//打开socket//传递空username,Opera产生一个登陆弹出框(#2865)if( s.username ) {

xhr.open( s.type, s.url, s.async, s.username, s.passWord);

}else{

xhr.open( s.type, s.url, s.async );

}//如果提供应用自定义字段if( s.xhrFields ) {for( iins.xhrFields ) {

xhr[ i ]=s.xhrFields[ i ];

}

}//重写mime类型,如果需要的话if( s.mimeType &&xhr.overrideMimeType ) {

xhr.overrideMimeType( s.mimeType );

}//X-Requested-With头if( !s.crossDomain && !headers["X-Requested-With"] ) {

headers["X-Requested-With"] = "XMLHttpRequest";

}//需要extra try/catch对跨域请求(Firefox 3中)try{for( iinheaders ) {

xhr.setRequestHeader( i, headers[ i ] );

}

}catch( err ) {}//发送请求//在jQuery.ajax中有try/catch处理xhr.send( ( s.hasContent && s.data ) ||null);//Listenercallback =function( _, isAbort ) {...};if( !s.async ) {//if we're in sync mode we fire the callbackcallback();

}elseif( xhr.readyState === 4) {//(IE6 & IE7) if it's in cache and has been//retrieved directly we need to fire the callbacksetTimeout( callback );

}else{

handle= ++xhrId;if( xhrOnUnloadAbort ) {//Create the active xhrs callbacks list if needed//and attach the unload handlerif( !xhrCallbacks ) {

xhrCallbacks={};

jQuery( window ).unload( xhrOnUnloadAbort );

}//Add to list of active xhrs callbacksxhrCallbacks[ handle ] =callback;

}

xhr.onreadystatechange=callback;

}

},

abort:function() {if( callback ) {

callback( undefined,true);

}

}

};

}

});

逻辑是比较简单的,就不详细分析了。可见普通情况下使用XHR方式来处理ajax。

如果觉得本文不错,请点击右下方【推荐】!

 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
 
© 2005- 王朝網路 版權所有 導航