jQuery-AJAX模块解析-request部分

说好的讲解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的格式
dataType
AJAX乱码问题
页面缓存
状态的跟踪
不同平台兼容
相信任何一个经验丰富的开发者都有遇到过上面的问题,每一个问题都令人头疼,所以得感谢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.ajaxTransport
function 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 dataTypeExpression
			while ( (dataType = dataTypes[i++]) ) {
				// Prepend if requested
				if ( 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 transports
function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) {
	//options为$.ajaxSettings
	var inspected = {},
		//是否探测transports
		seekingTransport = ( 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全局events
jQuery.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 set
	if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) {

		//略...

		// Delegate to script
		return "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 transport
jQuery.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 signature
	if ( typeof url === "object" ) {
		options = url;
		url = undefined;
	}

	// Force options to be an object
	options = options || {};

	var //一大堆变量我给删了
		s = jQuery.ajaxSetup( {}, options ),
		// Callbacks context
		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,
		// Deferreds,主延迟对象,将jqXHR做成一个promise对象
		deferred = jQuery.Deferred(),
		completeDeferred = jQuery.Callbacks("once memory"),
		// Fake xhr
		jqXHR = {
			//一大堆方法我给删了
		};

	// 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 available
	s.url = ( ( url || s.url || ajaxLocation ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" );

	// Alias method option to type as per ticket #12004
	s.type = options.method || options.type || s.method || s.type;

	// Extract dataTypes list
	s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().match( rnotwhite ) || [ "" ];

	// 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() );
		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 string
	if ( s.data && s.processData && typeof s.data !== "string" ) {
		s.data = jQuery.param( s.data, s.traditional );
	}
	//进行ajax过滤处理
	// Apply prefilters
	inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );

	// If request was aborted inside a prefilter, stop there
	if ( 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 type
	s.type = s.type.toUpperCase();

	//设置头信息和判断缓存的也给我删了

	// Allow custom headers/mimetypes and early abort
	if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
		// Abort if not done already and return
		return jqXHR.abort();
	}

	// aborting is no longer a cancellation
	strAbort = "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-abort
	if ( !transport ) {
		done( -1, "No Transport" );
	} else {
		jqXHR.readyState = 1;

		// Send global event
		//触发ajaxSend事件
		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;
			//done方法作为请求完成的complete函数调用
			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;
			}
		}
	}

	// Callback for when everything is done
	function 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 callbacks
		jqXHR.statusCode( statusCode );
		statusCode = undefined;

		//触发ajaxSuccess或者ajaxError事件
		if ( fireGlobals ) {
			globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",
				[ jqXHR, s, isSuccess ? success : error ] );
		}

		// Complete
		completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );

		//触发ajaxComplete事件
		if ( fireGlobals ) {
			globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
			// Handle the global AJAX counter
			if ( !( --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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值