jQuery-AJAX模块解析-request部分

来源:互联网 发布:太原医院挂号软件 编辑:程序博客网 时间:2024/05/01 14:57

说好的讲解ajax模块来着的,讲好的事我从来不骗人~(虽然拖了比较久,不过这是迟来的爱委屈)

首先来看下传统的Ajax请求代码:

function doAjax(config){var url = config.url,complete = config.complete,xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');xhr.open('post', url);xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');xhr.onreadystatechange = function() {if (xhr.readyState == 4) {if (xhr.status == 200) {complete(xhr.responseText);}}}xhr.send(config.data || null);return xhr;}

Ajax请求的流程一般为:

1、使用new XMLHttpRequest()或new ActiveXObject()插件的方式创建xhr对象。

2、通过xhr.open(...)的方式建立连接。

3、使用xhr.setRequestHeader(..)设置请求头信息

4、监听onreadystatechange事件,注册回调函数处理数据。

5、xhr.send(...)发生请求。

上面就是ajax的简单封装了,不过这么简陋的代码投入到生产中一般会被人打死。可以优化的地方太多,比如:提前浏览器特性检测、良好的API接口,对不同业务需求进行Ajax请求缓存优化等等,再如我们实际开发中会遇到很多问题:
跨域json的格式dataTypeAJAX乱码问题页面缓存状态的跟踪不同平台兼容
相信任何一个经验丰富的开发者都有遇到过上面的问题,每一个问题都令人头疼,所以得感谢jQuery提供的Ajax模块(jQuery大法好~)。

看下jQuery的ajax请求的优雅写法:

$.post(url, params).then(function(){  console.log('done');}, function(){  console.log('fail');});
不错!jQuery的接口留的就是这么优雅。真想给jQuery点32个赞~

首先因为jQuery的模块写的比较复杂(1200多行呢。。。),所以本篇blog只讲解Ajax请求的部分,response部分留到下次讲。

以下仅代表本人拙见,有错误或疑问之处欢迎之处~

jQuery的ajax的request部分应该有:预过滤器(ajaxPrefilter)、请求分发器(ajaxTransport)、json和script类型处理、jsonp的实现、请求头信息的处理和jQuery.ajax这个主方法。

先看ajaxPrefilter和ajaxTransport的构造器:

//addToPrefiltersOrTransports作为jQuery.ajaxPrefilter和jQuery.ajaxTransport的构造器,他们的方法都是通过addToPrefiltersOrTransports来构建一个闭包生成的函数,用于维持传进来的structure// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransportfunction addToPrefiltersOrTransports( structure ) {// dataTypeExpression is optional and defaults to "*"return function( dataTypeExpression, func ) {if ( typeof dataTypeExpression !== "string" ) {func = dataTypeExpression;dataTypeExpression = "*";}var dataType,i = 0,dataTypes = dataTypeExpression.toLowerCase().match( rnotwhite ) || [];if ( jQuery.isFunction( func ) ) {// For each dataType in the dataTypeExpressionwhile ( (dataType = dataTypes[i++]) ) {// Prepend if requestedif ( dataType.charAt( 0 ) === "+" ) {dataType = dataType.slice( 1 ) || "*";(structure[ dataType ] = structure[ dataType ] || []).unshift( func );// Otherwise append} else {//structure的dataType列表中push对应的过滤器处理函数来(structure[ dataType ] = structure[ dataType ] || []).push( func );}}}};}
ajaxPrefilter的API文档是这么写的:描述: 在每个请求之前被发送和$.ajax()处理它们前处理,设置自定义Ajax选项或修改现有选项。

ajaxTransport的API文档是这么写的:描述: 创建一个对象,用于处理Ajax数据的实际传输。


很明显可以看到这两个方法只是分别往对应的prefilters和transports的dataType中添加对应的处理函数func。

而他们进行探测的方法是inspectPrefiltersOrTransports:

//inspectPrefiltersOrTransports是一个prefilters和transports的探测器// Base inspection function for prefilters and transportsfunction inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) {//options为$.ajaxSettingsvar inspected = {},//是否探测transportsseekingTransport = ( structure === transports );function inspect( dataType ) {var selected;//防止重复探测inspected[ dataType ] = true;//fire该structure[ dataType ]中的所有处理函数jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) {var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR );if ( typeof dataTypeOrTransport === "string" && !seekingTransport && !inspected[ dataTypeOrTransport ] ) {options.dataTypes.unshift( dataTypeOrTransport );//递归探测处理函数返回的dataType,防止后添加的处理函数没有执行inspect( dataTypeOrTransport );return false;} else if ( seekingTransport ) {//如果dataTypeOrTransport可转为true,则停止探测return !( selected = dataTypeOrTransport );}});return selected;}return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" );}
ajaxPrefilter和ajaxTransport初始化给部分类型添加了对应的过滤器和分发器,如下:
// Handle cache's special case and global//处理缓存选项和设置跨域请求方式、禁止fire全局eventsjQuery.ajaxPrefilter( "script", function( s ) {if ( s.cache === undefined ) {s.cache = false;}if ( s.crossDomain ) {s.type = "GET";s.global = false;}});// Detect, normalize options and install callbacks for jsonp requests//像普通的方式处理json、jsonp格式的数据jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {var callbackName, overwritten, responseContainer,jsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ?"url" :typeof s.data === "string" && !( s.contentType || "" ).indexOf("application/x-www-form-urlencoded") && rjsonp.test( s.data ) && "data");// Handle iff the expected data type is "jsonp" or we have a parameter to setif ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) {//略...// Delegate to scriptreturn "script";}});jQuery.ajaxTransport(function( options ) {// Cross domain only allowed if supported through XMLHttpRequest//返回一个处理非跨域的transport对象if ( !options.crossDomain || support.cors ) {var callback;return {send: function( headers, complete ) {//略...},abort: function() {if ( callback ) {callback( undefined, true );}}};}});// Bind script tag hack transportjQuery.ajaxTransport( "script", function(s) {// This transport only deals with cross domain requests//返回一个处理跨域的transport对象if ( s.crossDomain ) {var script,head = document.head || jQuery("head")[0] || document.documentElement;return {send: function( _, callback ) {//略...},abort: function() {if ( script ) {script.onload( undefined, true );}}};}});

jQuery提供了一系列的预过滤器和分发器来处理常见dataType(如:script、json、jsonp)和跨域问题,在ajax发起请求之前做过滤处理,然后获取正确的transport对象来进行分发请求。

以上就是ajax的预过滤器和请求分发器了,jQuery通过这种预处理的方式将不同dataType的请求都提供了统一的处理接口,通过inspect transports获得正确的transport对象,然后使用统一的send接口发送请求,使用hack的方式处理了跨域请求,对内部$.ajax主方法完全解耦分离,还顺带给用户提供了过滤器和分发器功能。真是高明。


ajax的头信息就简单的说下好了,jqXHR(fake xhr)提供了一系列的方法来设置、获取头信息,另外在ajaxSettings中提供了cache选项来设置是否从缓存中获取数据。


而ajax主方法自从jQuery1.5之后就使用了Deferred延迟对象重写了:

ajax: function( url, options ) {// If url is an object, simulate pre-1.5 signatureif ( typeof url === "object" ) {options = url;url = undefined;}// Force options to be an objectoptions = options || {};var //一大堆变量我给删了s = jQuery.ajaxSetup( {}, options ),// Callbacks contextcallbackContext = s.context || s,// Context for global events is callbackContext if it is a DOM node or jQuery collectionglobalEventContext = s.context && ( callbackContext.nodeType || callbackContext.jquery ) ?jQuery( callbackContext ) :jQuery.event,// Deferreds,主延迟对象,将jqXHR做成一个promise对象deferred = jQuery.Deferred(),completeDeferred = jQuery.Callbacks("once memory"),// Fake xhrjqXHR = {//一大堆方法我给删了};// Attach deferreds//给jqXHR提供方便的API接口给用户deferred.promise( jqXHR ).complete = completeDeferred.add;jqXHR.success = jqXHR.done;jqXHR.error = jqXHR.fail;// Remove hash character (#7531: and string promotion)// Add protocol if not provided (#5866: IE7 issue with protocol-less urls)// Handle falsy url in the settings object (#10093: consistency with old signature)// We also use the url parameter if availables.url = ( ( url || s.url || ajaxLocation ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" );// Alias method option to type as per ticket #12004s.type = options.method || options.type || s.method || s.type;// Extract dataTypes lists.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().match( rnotwhite ) || [ "" ];// A cross-domain request is in order when we have a protocol:host:port mismatchif ( 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" ) ) ));}// Convert data if not already a stringif ( s.data && s.processData && typeof s.data !== "string" ) {s.data = jQuery.param( s.data, s.traditional );}//进行ajax过滤处理// Apply prefiltersinspectPrefiltersOrTransports( prefilters, s, options, jqXHR );// If request was aborted inside a prefilter, stop thereif ( state === 2 ) {return jqXHR;}// We can fire global events as of now if asked to// Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118)//是否触发全局事件fireGlobals = jQuery.event && s.global;// Watch for a new set of requests//一组ajax请求时,在第一个ajax发起时触发ajaxStart事件if ( fireGlobals && jQuery.active++ === 0 ) {jQuery.event.trigger("ajaxStart");}// Uppercase the types.type = s.type.toUpperCase();//设置头信息和判断缓存的也给我删了// Allow custom headers/mimetypes and early abortif ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {// Abort if not done already and returnreturn jqXHR.abort();}// aborting is no longer a cancellationstrAbort = "abort";// Install callbacks on deferreds//添加ajaxSettings中配置的对应函数for ( i in { success: 1, error: 1, complete: 1 } ) {jqXHR[ i ]( s[ i ] );}// Get transport//获得transport对象transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );// If no transport, we auto-abortif ( !transport ) {done( -1, "No Transport" );} else {jqXHR.readyState = 1;// Send global event//触发ajaxSend事件if ( fireGlobals ) {globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );}// Timeoutif ( s.async && s.timeout > 0 ) {timeoutTimer = setTimeout(function() {jqXHR.abort("timeout");}, s.timeout );}try {state = 1;//done方法作为请求完成的complete函数调用transport.send( requestHeaders, done );} catch ( e ) {// Propagate exception as error if not doneif ( state < 2 ) {done( -1, e );// Simply rethrow otherwise} else {throw e;}}}// Callback for when everything is donefunction done( status, nativeStatusText, responses, headers ) {var isSuccess, success, error, response, modified,statusText = nativeStatusText;//response的处理操作又给我删了// Success/Error//fire对应的回调列表if ( isSuccess ) {deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );} else {deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );}// Status-dependent callbacksjqXHR.statusCode( statusCode );statusCode = undefined;//触发ajaxSuccess或者ajaxError事件if ( fireGlobals ) {globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",[ jqXHR, s, isSuccess ? success : error ] );}// CompletecompleteDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );//触发ajaxComplete事件if ( fireGlobals ) {globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );// Handle the global AJAX counterif ( !( --jQuery.active ) ) {jQuery.event.trigger("ajaxStop");}}}//返回jqXHR对象,该对象是一个Promise对象return jqXHR;}
首先给jqXHR添加对应的接口,设置头信息和进行过滤操作,然后获取正确的transport对象,使用transport的send方法发起请求,,最后返回jqXHR。在done方法中处理response,最后延迟对象触发成功或失败的函数列表。其中在对应的时机触发对应的全局事件。

以上就是jQuery的ajax方法比较完整的流程了。


ajax的response部分和其他的零散部分留到下次再讲。

允许转载,请带上出处http://blog.csdn.net/z905951024 by denied.

0 0
原创粉丝点击