jQuery源码分析之ajaxTransport

其实际上和ajaxPrefilters逻辑一样,还是通过返回一个闭包函数来完成的。

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 ] = structure[ dataType ] || []).push( func );
				}
			}
		}
	};
}
ajaxTransport源码:
ajaxTransport: addToPrefiltersOrTransports( transports )

ajaxTransport其实是通过闭包来完成的,简洁版的如下:

function addToPrefiltersOrTransports(structure)//闭包不会释放对structure的引用,所以一直在内存中!
{
	return function(dataTypeExpression,func)
	{
		structure[dataTypeExpression]=structure[dataTypeExpression].push(func);	//一直引用外部参数!
	}
}
var prefilters={};//用于被外部函数引用,从而一直在内存中
function f1()
{
	alert("f1");
}
function f2()
{
	alert("f2");
}
var result=addToPrefiltersOrTransports(prefilters);
result("json",f1);
result("json",f2);
prefilters["json"][0]();//打印f1
prefilters["json"][1]();//打印f2

当然,简洁版的不满足在同一类型的回调函数的头部添加,如果要在回调函数的头部添加可以通过在前面添加加号来完成

var func1=function(){alert(2);}
var func=function(){alert(1);}
var prefilters={};
//返回匿名函数
var resultFunc=addToPrefiltersOrTransports(prefilters);
//这时候prefilters里面已经被添加了func函数了
//所以prefilters["jsonp"]=[func],prefilters["html"]=[func]
resultFunc("jsonp html",func);
resultFunc("+jsonp +html",func1);
alert(prefilters["jsonp"]);
//打印数组[func1,func]也就是如果在前面添加一个加号,那么就会加入到prefilters或者transport的最前面,这是unshift和push的区别
//push表示在后面添加。如果上面没有加号,那么打印就是[func,func1]
note:通过在调用返回的闭包函数添加的类型前面添加加号,可以在回调类型集合的前面添加回调, 因为特定类型的回调函数在prefilters或者transpot中都是集合!注意,如果调用闭包函数没有传入第一个参数,那么就会添加到以*为键名的集合中,而不是添加到如prefilters["json"],而是prefilters["*"]。

ajaxTransport对script标签的特殊处理:

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 = document.createElement("script");
				script.async = true;
				if ( s.scriptCharset ) {//只有dataType是"script","jsonp"并且type是get时候才会修改!
					script.charset = s.scriptCharset;
				}
				script.src = s.url;
				// Attach handlers for all browsers
				script.onload = script.onreadystatechange = function( _, isAbort ) {
					if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) {
						// 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
				head.insertBefore( script, head.firstChild );
			},
			abort: function() {
				if ( script ) {
					script.onload( undefined, true );
				}
			}
		};
	}
});

dataTypeString类型

默认值:jQuery智能猜测,猜测范围(xml、 json、 script或html)指定返回的数据类型。该属性值可以为:

  • 'xml' :返回XML文档,可使用jQuery进行处理。
  • 'html': 返回HTML字符串。
  • 'script': 返回JavaScript代码。不会自动缓存结果。除非设置了cache参数。注意:在远程请求时(不在同一个域下),所有POST请求都将转为GET请求。(因为将使用DOM的script标签来加载)。注意:如果是跨域的情况下,处理script的数据类型就会按照上面的ajaxTransport函数。否则还是通过通用的ajaxTransport返回"string"。
  • 'json': 返回JSON数据。JSON数据将使用严格的语法进行解析(属性名必须加双引号,所有字符串也必须用双引号),如果解析失败将抛出一个错误。从jQuery 1.9开始,空内容的响应将返回null{}
  • 'jsonp': JSONP格式。使用JSONP形式调用函数时,如"url?callback=?",jQuery将自动替换第二个?为正确的函数名,以执行回调函数。
  • 'text': 返回纯文本字符串。

scriptCharsetString类型

设置该请求加载的脚本文件的字符集。只有当请求时dataType为"jsonp"或"script",并且type是"GET"才会用于强制修改charset。这相当于设置<script>标签的charset属性。通常只在当前页面和远程数据的内容编码不同时使用。

asyncBoolean类型

默认值:true

指示是否是异步请求。同步请求将锁定浏览器,直到获取到远程数据后才能执行其他操作。

note:该ajaxTransport函数只是为了处理跨域的请求,而且处理的是script标签的跨域请求!他内部的逻辑还是很简单的,只是创建一个script标签,同时async设为true表示异步的,同时监听onload和onreadyStateChange事件!

如果浏览器能够返回XHR对象那么添加一个ajaxTransport:

var xhrSupported = jQuery.ajaxSettings.xhr();//返回一个XHR对象
support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported );
xhrSupported = support.ajax = !!xhrSupported;	
//他调用上面的ajaxTransport方法返回的对象,并且传入一个函数!那么这个函数被放到transports对象中!
	jQuery.ajaxTransport(function( options ) {
		// Cross domain only allowed if supported through XMLHttpRequest
		//如果没有执行跨域,但是支持cors
		if ( !options.crossDomain || support.cors ) {
			var callback;
			return {
				//send方法
				send: function( headers, complete ) {
					var i,
						//获取xhr对象
						xhr = options.xhr(),
						//保存xhr的数量
						id = ++xhrId;
                                             //打开socket!
					// Open the socket
					xhr.open( options.type, options.url, options.async, options.username, options.password );


					// Apply custom fields if provided
					//如果options提供了xhrFields,如options.xhrFields={name:"xxx",sex:"female"}
					//最终就是xhr["name"]="xxx",xhr["sex"]="female"
					if ( options.xhrFields ) {
						for ( i in options.xhrFields ) {
							xhr[ i ] = options.xhrFields[ i ];
						}
		 			}
                                  //如果提供了mimeType那么覆盖本身的mimeType类型!
					// 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.
					//如果没有提供crossDomain同时header里面也没有X-Requested-With
					//那么headers["X-Requested-With"]就是XMLHttpRequest
					// X-Requested-With请求头用于在服务器端判断request来自Ajax请求还是传统请求。
 				   // 两种请求在请求的Header不同,Ajax 异步请求比传统的同步请求多了一个头参数
                                    //但是要设置 X-Requested-With头要满足双重条件才可:
					//  (1)没有crossDomain
					//  (2)X-Requested-With不存在!如果存在就不要这里设置,因为已经在headers里面了!
				
					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)
					//	s.hasContent = !rnoContent.test( s.type );
					//rnoContent = /^(?:GET|HEAD)$/,
					//get和head请求没有数据
					//这里就是说:如果不是get,head请求同时options有data那么必须要在send方法传出去
					//如果是get,head请求那么直接传送null!
					xhr.send( ( options.hasContent && options.data ) || null );
					// Listener
					callback = function( _, isAbort ) {
						var status, statusText, responses;
                                      //如果callbacks存在,同时isAbort为false表示没有放弃执行
						// Was never called and is aborted or complete
						if ( callback && ( isAbort || xhr.readyState === 4 ) ) {
							// Clean up
							//清除回调!同时从xhrCallbacks中清除!
							delete xhrCallbacks[ id ];
							callback = undefined;
							//将事件变化函数绑定为空函数
							xhr.onreadystatechange = jQuery.noop;
                                          //如果要移除,而且readyState不是4,那么手动调用abort方法
							// Abort manually if needed
							if ( isAbort ) {
								if ( xhr.readyState !== 4 ) {
									xhr.abort();
								}
							} else {
								//如果不是调用abort,那么这时候isAbort就是undefined!
								responses = {};
								status = xhr.status;
								// Support: IE<10
								// Accessing binary-data responseText throws an exception
								// (#11426)
								//如果responseText是string那么responses的text就是responseText
								//IE<10用responseText获取二进制数据时候报错!
								if ( typeof xhr.responseText === "string" ) {
									responses.text = xhr.responseText;
								}
                                                      //火狐在用statusText处理跨域请求时候报错!
								// 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)
							//允许将当前环境视作"本地",(例如文件系统),即使默认情况下jQuery不会如此识别它。
								//目前,以下协议将被视作本地:file、*-extension和widget。
								//如果status不存在,同时isLocal是true,同时crossDomain是false
								//那么如果responses.text存在那么status就是200,否则就是404!
								if ( !status && options.isLocal && !options.crossDomain ) {
									status = responses.text ? 200 : 404;
								// IE - #1450: sometimes returns 1223 when it should be 204
								//IE下有时候返回1123,但是应该是204!
								} else if ( status === 1223 ) {
									status = 204;
								}
							}
						}
						// Call complete if needed
						if ( responses ) {
							complete( status, statusText, responses, xhr.getAllResponseHeaders() );
						}
					};
                  //如果没有指定async那么直接调用callback方法!
					if ( !options.async ) {
						// if we're in sync mode we fire the callback
						callback();
					} else if ( xhr.readyState === 4 ) {
						//在IE6,7中,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
						
						//var xhrId = 0,
						//xhrCallbacks = {}
						//xhrCallbacks[i]=callback说明这个对象是为了保存回调函数的!
						xhr.onreadystatechange = xhrCallbacks[ id ] = callback;
					}
				},
                 //如果调用abort方法,那么回调的时候第一个参数是undefined,第二个参数是true
				abort: function() {
					if ( callback ) {
						callback( undefined, true );
					}
				}
			};
		}
	});

note:

(1)CrossDomain

默认值:同域请求为false,跨域请求为true指示是否是跨域请求。如果你想在同一域中强制跨域请求(如JSONP形式),请设置为true。例如,这允许服务器端重定向到另一个域。其实ajax函数内部会自动判断是否是跨域,如果是跨域那么就会把它设置为true,通过比较协议名称,域名,端口号来完成!

(2)调用步骤

(1)open方法打开socket流,同时为XHR对象添加HTTP头部信息
(2)overrideMimeType方法,该方法的调用要在send方法之前才能保证修改mimeType!迫使XHR对象把相应当作特定格式处理!
(3)post请求通过send方法发送数据,get请求可以在send方法中也可以在URL后面!

(3)get,head请求不同

 get,head请求是没有数据的,参数可以直接在URL里面,所以直接发送null,对于其它的请求必须把数据放在send方法里面发送出去!

你可能感兴趣的:(jQuery源码分析之ajaxTransport)