jQuery源码分析之ajaxConverter与ajaxHandleResponse函数

ajaxSetup源码分析:

ajaxSetup: function( target, settings ) {
		return settings ?
			// Building a settings object
			ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :
			// Extending ajaxSettings
			ajaxExtend( jQuery.ajaxSettings, target );
	}
note:下面对于"text script"或者"text jsonp"这种类型我们只是传入了一个参数, 最后我们把这些属性全部封装到了jQuery.ajaxSettings方法上面!也就是我们最后的options对象!

对于“text script”的类型的处理:

jQuery.ajaxSetup({
	accepts: {
		script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
	},
	contents: {
		script: /(?:java|ecma)script/
	},
	converters: {
		"text script": function( text ) {
			jQuery.globalEval( text );
			return text;
		}
	}
});
note:直接调用了ajaxSetup方法,所以这个converters也会放入到最终的options中间,所以如果你在dataType里面传入"text script",那么就会按照这种方式来解析。也就是直接调用jQuery.globalEval函数, 最终如果用户的dataType是"text script"那么结果就是直接执行!
如果传入的dataType是"jsonp"那么会怎么处理呢,看下面的源码:

jQuery.ajaxSetup({
	jsonp: "callback",
	jsonpCallback: function() {
		var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce++ ) );
		this[ callback ] = true;
		return callback;
	}
});
对于json和jsonp数据都是经过预先处理的,也就是说如果我们在dataType中传入了json或者jsonp那么我们在发送ajax请求之前都是经过特殊处理的!

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" ) {
		// Get callback name, remembering preexisting value associated with it
		callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ?
			s.jsonpCallback() :
			s.jsonpCallback;
		// Insert callback into url or form data
		if ( jsonProp ) {
			s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName );
		} else if ( s.jsonp !== false ) {
			s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName;
		}
		// Use data converter to retrieve json after script execution
		s.converters["script json"] = function() {
			if ( !responseContainer ) {
				jQuery.error( callbackName + " was not called" );
			}
			return responseContainer[ 0 ];
		};
		// force json dataType
		s.dataTypes[ 0 ] = "json";
                //如果是json或者jsonp请求,那么我们最后把dataTypes[0]修改为json,那么我们最后服务器返回数据的时候
                //就可以在converter里面找到json是按照parseJSON方法来完成的!
		// Install callback
		overwritten = window[ callbackName ];
		window[ callbackName ] = function() {
			responseContainer = arguments;
		};
		// Clean-up function (fires after converters)
		jqXHR.always(function() {
			// Restore preexisting value
			window[ callbackName ] = overwritten;
			// Save back as free
			if ( s[ callbackName ] ) {
				// make sure that re-using the options doesn't screw things around
				s.jsonpCallback = originalSettings.jsonpCallback;
				// save the callback name for future use
				oldCallbacks.push( callbackName );
			}
			// Call if it was a function and we have a response
			if ( responseContainer && jQuery.isFunction( overwritten ) ) {
				overwritten( responseContainer[ 0 ] );
			}
			responseContainer = overwritten = undefined;
		});
		// Delegate to script
		return "script";
	}
});

在讲ajaxConverter之前我们首先必须熟悉ajaxHandleResponse。为什么这么说呢,因为如果我们传入的dataType只有一个,如"json",那么你可能认为我们最后的s.dataType只有["json"],其实不是这样的,最后的格式其实是["text","json"]而这个text的加入就来自于ajaxHandleResponse函数。

(1)只要你传入的dataType是单个类型,都会经过他的处理,如"json","jsonp","html","script"最后都会变成["text","XX"]这种类型。那么他是怎么做到的呢?那么你必须了解通用ajaxTransport,他最后得到服务器的返回的数据类型是{"text":xhr.responseText}类型,所以在ajaxHandleResponse中得到的finalDataType就是text,而dataType[0]就是你传入的数据类型。上面我没有说用户传入*的情况,这种情况jQuery根据contentType来判断,在最后的dataTypers中会把*移除掉,转化成为服务器端返回的数据类型,如服务器返回json,那么最后的结果就是["text","json"]但是还是要弄懂通用ajaxTransport返回的数据是{"text":xhr.responseText}类型!

(2)ajaxHandleResponse的第二个作用就是把服务器的数据原封不动的传递给ajaxConverter,同时传入要转换的数据类型,如["text","json"]左后就会转化为json!

(3)题外话,post类型的ajax请求不会缓存,因为通过firebug将始终看到200,不会看到304状态码!

function ajaxHandleResponses( s, jqXHR, responses ) {
	var firstDataType, ct, finalDataType, type,
		contents = s.contents,
		dataTypes = s.dataTypes;
	// Remove auto dataType and get content-type in the process
	while ( dataTypes[ 0 ] === "*" ) {//如果用户传入的dataType是*就会经过这里的逻辑,最后通过mimeType或者返回的Content-Type来决定!
		dataTypes.shift();//如果服务器端返回的是json,那么ct就是application/json;charset=utf-8!shift就是把*移除!
		if ( ct === undefined ) {
			ct = s.mimeType || jqXHR.getResponseHeader("Content-Type");
		}
	}
	// Check if we're dealing with a known content-type
	if ( ct ) {//这时候根据content-type来处理,如application/json;charset=utf-8
	  for ( type in contents ) {//我们的contents中有"xml,html,json",content[type]是正则表达式,如果服务器返回json,那么满足正则!
			if ( contents[ type ] && contents[ type ].test( ct ) ) {
				dataTypes.unshift( type );//最后添加的dataTypes就是["json"],因为*已经被移除!
				break;
			}
		}
	}
	// Check to see if we have a response for the expected dataType
	//我们知道通用的ajaxTransport都是通过responseText方式返回的
	//所以他的格式是responses={"text":xhr.responseText}
	//所以这里的,所以这里的if不会执行!
	if ( dataTypes[ 0 ] in responses ) {
		finalDataType = dataTypes[ 0 ];
	} else {
		// Try convertible dataTypes
		//因为这里的type是"text",同时dataTypes[0]是json,s.converters["text json"]
		//所以finalDataType就是text!
		for ( type in responses ) {
			if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) {
				finalDataType = type;//如果传入的是*,这里的finalDataType也是"text",所以最后的dataTypes就是["text json"]
				break;
			}
			if ( !firstDataType ) {
				firstDataType = type;
			}
		}
		// Or just use first one
		finalDataType = finalDataType || firstDataType;
	}
	// If we found a dataType
	// We add the dataType to the list if needed
	// and return the corresponding response
	if ( finalDataType ) {
		//finalDataType是text,dataTypes[0]是json
		//所以这里的dataTypes变成了["text json"]
		//unshift表示在最前面添加!
		if ( finalDataType !== dataTypes[ 0 ] ) {
			dataTypes.unshift( finalDataType );//这里才真正把dataTypes变成["text","XXX"]类型!
		}
		return responses[ finalDataType ];
	}
}

下面给出ajaxConverter的源码(通过上面的分析你应该知道了,dataTypes[1]是始终存在的!ajaxConverter就根据上面ajaxHandleReponse提供的dataTypes对数据进行转换):

function ajaxConvert( s, response, jqXHR, isSuccess ) {
	var conv2, current, conv, tmp, prev,
		converters = {},
		// Work with a copy of dataTypes in case we need to modify it for conversion
		dataTypes = s.dataTypes.slice();
	// Create converters map with lowercased keys
	if ( dataTypes[ 1 ] ) {
		for ( conv in s.converters ) {
			//converters["* text"]=window.String
			//converters["text html"]=true
			//converters["text json"]=jQuery.parseJSON
			//converters["text xml"]=jQuery.parseXML
			converters[ conv.toLowerCase() ] = s.converters[ conv ];
		}
	}
	//拿出第一项,这里为text
	current = dataTypes.shift();
	// Convert to each sequential dataType
	//拿出text第一项
	while ( current ) {
		//第一次打印"html",
		//alert(s.responseFields[ current ] );
		if ( s.responseFields[ current ] ) {
			//把jqXHR["responseHTML"]={}
			//jqXHR["responseText"]={}
			jqXHR[ s.responseFields[ current ] ] = response;
		}
		// Apply the dataFilter if provided
		if ( !prev && isSuccess && s.dataFilter ) {
			response = s.dataFilter( response, s.dataType );
		}
		prev = current;
		//然后继续获取第一项,这时候prev是text,current是json
		current = dataTypes.shift();
		if ( current ) {
              //current这时候是json
			 //alert(current),如果第二项是*那么操作的还是第一项!
			 // There's only work to do if current dataType is non-auto
			if ( current === "*" ) {
				current = prev;
			// Convert response if prev dataType is non-auto and differs from current
			} else if ( prev !== "*" && prev !== current ) {
				// Seek a direct converter
				conv = converters[ prev + " " + current ] || converters[ "* " + current ];
               //这时候prev + " " + current="text json"所以conv=jQuery.parseJSON,所以下面不会走if语句!
				// If none found, seek a pair
				if ( !conv ) {
					//converters["* text"]=window.String
					//converters["text html"]=true
					//converters["text json"]=jQuery.parseJSON
					//converters["text xml"]=jQuery.parseXML
					for ( conv2 in converters ) {
						// If conv2 outputs current
						//把converters的键名全部按照空格切开!
						tmp = conv2.split( " " );
	//如果切开过后的第二项和current相等,所以在传入的参数converters中,键值还是第二项最重要,因为第一项都是text没有实际的意义!
	//因为这里的current是json,所以一直会切换到第三个,也就是converters["text json"]才会满足if!
						//也就是说如果在converters里面第二项和我在dataTypes里面的第二项相同那么就满足if语句了!
						if ( tmp[ 1 ] === current ) {
		//加入上面的current是json,也就是dataType是"text json"那么这里切割的就是converters["text json"]=jQuery.parseJSON
							//temp[1]="json",temp[0]="text"
							//conv=converters["text text"]||converters["* text"]
							// If prev can be converted to accepted input
							conv = converters[ prev + " " + tmp[ 0 ] ] ||
								converters[ "* " + tmp[ 0 ] ];
							//conv存在
							if ( conv ) {
								// Condense equivalence converters
								//表示dataType是"text html",那么这里就是converters[""]
								if ( conv === true ) {
									conv = converters[ conv2 ];
								// Otherwise, insert the intermediate dataType
								} else if ( converters[ conv2 ] !== true ) {
									//current设置为""
									current = tmp[ 0 ];
									dataTypes.unshift( tmp[ 1 ] );
								}
								break;
							}
						}
					}//End of for loop
				}
                 //因为上面的conv不是true,conv=jQuery.parseJSON
				// Apply converter (if not an equivalence)
				if ( conv !== true ) {
					// Unless errors are allowed to bubble, catch and return them
					if ( conv && s[ "throws" ] ) {
						response = conv( response );
					} else {
						try {
							//用找到的处理函数对结果进行处理
							response = conv( response );
					//因为我传入的dataType为"text json"所以直接用parseJSON进行处理得到了Object对象
				//这也是为什么直接在$.ajax方法里面传入的"text json"那么返回的结果就是JSON的原因!
							//alert(response);
						} catch ( e ) {
			return { state: "parsererror", error: conv ? e : "No conversion from " + prev + " to " + current };
						}
					}
				}
			}//End of outer elseIf
		}//End of if
	}//End of while

	//(1)如果传入的是jsonp,那么不会进行处理,直接返回这个对象,这个对象的第一个键名是state键值是success
	//第二个键名是data键值是response!也就是对jsonp等数据不进行特殊的处理!
	//(2)如果传入的xml或者text,json等单个字符串,那么也不会把数据放入converters里面,因为dataType[1]为空!但是这时候为jqXHR["responseXML"]=response
	//jqXHR["responseText"]=response,jqXHR["responseJSON"]=response,其中response就是返回的数据!但是最后返回的数据还是和第一种情况是一样的!
	//(3)如果传入的是"text json",那么converters["* text"]=window.String,converters["text html"]=true,converters["text json"]=jQuery.parseJSON
	//converters["text xml"]=jQuery.parseXML,这时候current就是json,prev就是text,这时候conv=converters["text json"]=jQuery.parseJSON
	//然后把response数据传入conv中进行处理,然后返回数据形式{state:"success",data:"response"}不过这个response是已经解析为JSON的数据了!
	
	return { state: "success", data: response };
}

总结:

(1)ajaxSetup如果没有第二个参数那么就是参数全部封装到jQuery.ajaxSettings上面。如果传入了第二个参数,那么首先把jQuery.ajaxSettings封装到第一个参数上,最后把第二个参数封装到上一步返回的对象上面,结果就是第一个参数具有了ajaxSetting和第二个参数的所有的方法和属性!

(2)对于"text script"类型jQuery自动添加了一个converter,这个converter是按照jQuery.globalEval方式来运行结果的!这就处理了dataType是script类型,但是服务器返回的是string类型,把string类型转换为js运行的逻辑!

(3)对于json,jsonp这种dataType来说,我们在内部直接将dataType[0]设置为"json",那么我们把服务器返回的数据按照parseJSON的方式进行处理!同时从上面的源码中也可以看到如果dataTypes为"script json"那么这种处理的函数处理逻辑其实也很简单。如果返回的有数据那么直接返回数据,如果没有数据,那么我们直接报错!

(4)我们要知道通过ajaxSetup或者ajaxPrefilter,ajaxTransport这种方式都会改变jQuery.ajaxSetting内置的converters数组!

(5)dataFilterFunction类型

指定处理响应的原始数据的回调函数。该函数还有两个参数:其一表示响应的原始数据的字符串,其二是dataType属性字符串。这个函数就是在ajaxConverter里面被调用的,因为dataFilter方法生来就是为处理响应数据服务的,不过他只是对响应数据的预处理,真正的处理逻辑还要通过ajaxConverter函数内部的逻辑来完成,如数据类型的转换!从string类性转换为json等类型!

(6)该方法中首先判断是否含有dataType[1],目地在于判断如果是存在,那么就可以通过内置的converters来完成,因为内置的converters都是双项的!于是形成converters{"text html",true,"text xml":parseXML}这种对象可以直接处理双项的数据的转换!

                       "* text": String,
			// Text to html (true = no transformation)
			"text html": true,
			// Evaluate text as a json expression
			"text json": jQuery.parseJSON,
			// Parse text as xml
			"text xml": jQuery.parseXML
note:如果上面的双项的数据类型不能处理,那么我们就进行数据类型切开,通过判断"text xml"中的xml是否和传入的dataTypes[1]相等,如果相等,那么构建新的转换器conv = converters[ prev + " " + tmp[ 0 ] ] ||converters[ "* " + tmp[ 0 ] ];来处理,所以他的逻辑还是很简单的!不过一般我们传入的还是"text xml"等固定的形式!这里只是为了让jQuery更加灵活一点!该方法的返回数据格式为: { state: "success", data: response }这种类型!state表示成功与否,data就是经过转换的数据!

你可能感兴趣的:(jQuery源码)