jQuery源码分析之ajaxPrefilters方法

prefilters源码分析(主要作用在于调用send之前对HTTP头等进行修改和进一步处理):

	ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
可以看到内部直接调用了addToPrefiltersOrTransports方法

var rnotwhite = (/\S+/g);
function addToPrefiltersOrTransports( structure ) {
    //该函数可以传递两个参数,第一个是dataType组成的字符串,用空格隔开
	//第二个参数是回调函数!
	// dataTypeExpression is optional and defaults to "*"
	return function( dataTypeExpression, func ) {
        //如果传入的dataTypeExpression不是string,记住dataTypeExpression默认为"*"
		//那么表示只是传入了一个参数,该参数就是回调函数!
		if ( typeof dataTypeExpression !== "string" ) {
			func = dataTypeExpression;
			dataTypeExpression = "*";
		}
		var dataType,
			i = 0,
			//把dataTypeExpression修改为一个数组!
			dataTypes = dataTypeExpression.toLowerCase().match( rnotwhite ) || [];
          //如果传入的第二个参数是函数
		if ( jQuery.isFunction( func ) ) {
			// For each dataType in the dataTypeExpression
			while ( (dataType = dataTypes[i++]) ) {
				// Prepend if requested
				//如果dataType第一个字符是+,那么获取+以后的部分!
				if ( dataType.charAt( 0 ) === "+" ) {
					dataType = dataType.slice( 1 ) || "*";
					//shift用于移除数组中第一项并且返回该项,unshift用于在数组前面添加任意项并且返回数组长度!
			//如果要过滤"html jsonp"那么一开始传入structure是空对象,那么structure["html"]=[],然后把这个回调函数
			//插入到数组的最前面,记住这里传入的structure是一个引用,所以当你这里修改为structure["html"]=[func]时候那么
			//原来的prefilters对象也已经修改了!记住,如果是+调用的是unshift那么是放在数组最前面的!
					(structure[ dataType ] = structure[ dataType ] || []).unshift( func );
                 //如果没有+那么是放在回调数组的后面的!
				// Otherwise append
				} else {
					(structure[ dataType ] = structure[ dataType ] || []).push( func );
				}
			}
		}
	};
}
测试代码1:

var func=function(){alert(1);}
var prefilters={};
//返回匿名函数
var resultFunc=addToPrefiltersOrTransports(prefilters);
//这时候prefilters里面已经被添加了func函数了
//所以prefilters["jsonp"]=[func],prefilters["html"]=[func]
resultFunc("jsonp html",func);
//里面存储的是函数,可以直接调用!
prefilters["jsonp"][0]();

测试代码2:(jQuery对scripts类型的预先处理)

var prefilters={};
var options={};
jQuery.ajaxPrefilter1=addToPrefiltersOrTransports(prefilters);
jQuery.ajaxPrefilter1( "script", function( s ) {
	if ( s.cache === undefined ) {
		s.cache = false;
	}
	if ( s.crossDomain ) {//记住,如果是跨域请求那么不会相应全局ajax事件!
		s.type = "GET";
		s.global = false;
	}
});
//这是prefilters["scripts"]是一个数组,获取第0项就是我上面添加的函数,然后执行!
alert(prefilters["script"][0](options));

dataTypeString类型

默认值:jQuery智能猜测,猜测范围(xml、 json、 script或html)

指定返回的数据类型。该属性值可以为:

  • 'xml' :返回XML文档,可使用jQuery进行处理。
  • 'html': 返回HTML字符串。
  • 'script': 返回JavaScript代码。不会自动缓存结果。除非设置了cache参数。注意:在远程请求时(不在同一个域下),所有POST请求都将转为GET请求。(因为将使用DOM的script标签来加载)
  • 'json': 返回JSON数据。JSON数据将使用严格的语法进行解析(属性名必须加双引号,所有字符串也必须用双引号),如果解析失败将抛出一个错误。从jQuery 1.9开始,空内容的响应将返回null{}
  • 'jsonp': JSONP格式。使用JSONP形式调用函数时,如"url?callback=?",jQuery将自动替换第二个?为正确的函数名,以执行回调函数。
  • 'text': 返回纯文本字符串。
  • 注意codeplayer上面的这一个关于"script"的解释我来说明一下:通过上面的ajaxPrefilters可以看出如果在请求script的时候没有设置cache为true,那么"script"类型的请求是不会缓存的,不管跨域请求与否都不会缓存!同时如果是跨域请求的话,我们采用的是get方法,因为我们通过的是DOM的script标签来加载的!
  • 通过对ajaxTransport的逻辑分析我们可以看到,如果在跨域的情况下,除了满足上面的ajaxPrefilters的处理逻辑,我们还会进行特殊的处理。如下面的scriptCharset的处理,也就是说当跨域的情况下,dataTypes是"script"的时候,除了把type修改为get以外,同时也可以自定义scriptCharset!
  • scriptCharsetString类型

    设置该请求加载的脚本文件的字符集。只有当请求时dataType为"jsonp"或"script",并且type是"GET"才会用于强制修改charset(其实必须是跨域请求才会修改scriptCharset)。这相当于设置<script>标签的charset属性。通常只在当前页面和远程数据的内容编码不同时使用。ajaxPrefilters在处理"jsonp,json"的时候返回了一个"script",最终导致在insprctPrefiltersOrTransport中也会对"script"进行处理,也就是调用ajaxPrefilters中关于"script"的拦截器,执行"script"中的处理函数集合!进而也达到:如果没有明确指定cache,那么jsonp不会缓存;如果跨域,那么type就是get,同时ajax全局事件失效;但是这仅仅是ajaxPrefilters中的对HTTP头的处理,最终在transport中还是用了通用的ajaxTransport,因为没有对于"jsonp"的特殊的ajxTransport,所以最后得到的还是一个字符串,然后通过ajaxConverter对这个字符串进行了修改,执行得到的jsonp请求结果!

下面是jQuery内部对jsonp或者json进行的预处理:

// Detect, normalize options and install callbacks for jsonp requests
jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
	var callbackName, overwritten, responseContainer,
		jsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ?
         url" ://指定了是jsonp请求,同时url也符合jsonp规范,那么jsonProp就是字符串"url"。
        //否则,只有data是string,contentType是x-www-form-urlencoded,同时s.data符合特定的规范时候才会是data字符串
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";
		// Install callback
		overwritten = window[ callbackName ];//把自己设置的或者jQuery默认的回调函数保存下来,进而得到数据时候回调!
		window[ callbackName ] = function() {//之所以要重写一个函数,是为了回调时候获取到服务器数据,而且是类数组的数据格式!
			responseContainer = arguments;
		};
		// Clean-up function (fires after converters)
		jqXHR.always(function() {//这个方法加入到jqXHR的always中,表示不管对服务器的请求是失败还是成功都应该回调!
			// Restore preexisting value
			window[ callbackName ] = overwritten;//重新载入回调函数,也就是我们设置的或者jQuery默认的回调函数!
			// 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 );//这句if,使得我们成功或者失败时候重新保证我们没有对options修改掉!
			}
			// Call if it was a function and we have a response
			if ( responseContainer && jQuery.isFunction( overwritten ) ) {
		         overwritten( responseContainer[ 0 ] );//真正回调,因为服务器返回的参数是字符串,该字符串已经在arguments中!
			}//这个回调结束了,那么jsonp请求才算真正完成了!只要resolve或者reject调用时候就会触发!
			responseContainer = overwritten = undefined;
		});

		// Delegate to script
		return "script";
	}
});

特殊情况:当用户在URL中指定了jsonp函数,我们只要手动添加jsonpCallback函数就可以了:

var rjsonp = /(=)\?(?=&|$)|\?\?/;
var s={
	url:"http://localhost:8080/qinl/a.action?name=?",/*相当于指定了jsonp函数,但是jsonCallback函数必须自己手动添加上去*/
	jsonpCallback:"qinliang"
};
console.log( rjsonp.test( s.url ));/*打印true,表示符合jsonp的URL格式,只是jsonp函数已经在URL中指定了!*/
/*对URL重新设置,添加jsonCallback函数*/
var result=s["url"].replace(/(=)\?(?=&|$)|\?\?/,"$1" + s.jsonpCallback );
/*打印http://localhost:8080/qinl/a.action?name=qinliang*/
console.log(result);

测试代码2:

var rquery = (/\?/); 
var rjsonp = /(=)\?(?=&|$)|\?\?/;
        var s={
	jsonp:"onjsonload",
			   url:"a.action?name=?",
	          callbackNames:"invoke"
			 }
//打印a.action?name=?&onjsonload=invoke
var result=s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + s["callbackNames"];
alert(result);

note:在这个函数内部我们是对jsonp的请求的url进行了处理,得到正确的URL进行访问。解决的特殊情况:如果用户URL后有问号,那么添加&符号就行!

ajaxPrefilters中函数的执行:

function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) {

	var inspected = {},
		seekingTransport = ( structure === transports );
	function inspect( dataType ) {
		var selected;
		inspected[ dataType ] = true;
		jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) {
			var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR );
			if ( typeof dataTypeOrTransport === "string" && !seekingTransport && !inspected[ dataTypeOrTransport ] ) {
				options.dataTypes.unshift( dataTypeOrTransport );
				inspect( dataTypeOrTransport );
				return false;
			} else if ( seekingTransport ) {
				return !( selected = dataTypeOrTransport );
			}
		});
		return selected;
	}
//(1)text类型没有prefilters,接下来处理*类型的回调函数!所以我们还是直接指定"json"等单个字符串,而不要是"text json"这种类型,否则只会执行text的prefilters,
//而text的prefiltes是不存在的,更加糟糕的是prefiltes也没有是*的这种类型,所以prefilters会形同虚设!
//(2)ajaxTransport类型同样的道理也不要设置为"text json"而要设置为"json"这种格式。如果没有json这种ajaxTransport那么就会处理*这种类型,但是json这种存在!
//ajaxPrefiltes处理script和json/jsonp格式;ajaxTransport处理script和*这种类型!
	return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" );
}
note: 我们指定数据类型的时候还是应该指定"json"而不要指定"text json"否则所有的关于json的prefilters形同虚设!ajaxPrefilters处理script/json/jsonp类型,而ajaxTransport处理了script/*这种类型!

总结:

(1)这里调用addToPrefiltersOrTransports方法时候是返回一个匿名函数,传入的参数会被匿名函数保存起来,这就是闭包!同时,在函数里面修改了这个prefilters对象那么外层的函数是可以检测到的,否则ajax方法里面就无法调用这些预处理函数了!

(2)如果在给prefilters方法,也就是匿名函数传入的第一个字符串参数前面有加号,那么内部会调用unshift把这个函数放在数组的最前面,如果没有加号那么就只是在数组里面添加

(3)(structure[ dataType ] = structure[ dataType ] || [])代码告诉我们这个每一个类型如html类型的预处理函数是一个数组,这个数组里面会存放很多的预处理函数!

(4)

用于预处理参数选项的回调函数。它有以下3个参数:

  • options:(Object对象)当前AJAX请求的所有参数选项。
  • originalOptions:(Object对象)传递给$.ajax()方法的未经修改的参数选项。
  • jqXHR:当前请求的jqXHR对象(经过jQuery封装的XMLHttpRequest对象)。

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