上一篇文章 ,将jQuery.ajax中的一些细节补充完。这篇文章讲解如果将类AJAX方法都包装进jQuery.ajax中。下篇文章则讲解各预过滤器和分发器的细节。
为什么要包装起来?
我们知道,古老的XMLHttpRequest出于同源策略考虑,是不支持跨域的。所以,在前端想动态加载跨域Javascript脚本,通常是使用被称为Script DOM Element的方案,如:
var scriptElem = document.createElement("script" ); scriptElem.src = "http://anydomain.com/A.js" ; document.getElementsByTagName( "head")[0].appendChild(scriptElem);
同理,JSON也无法通过XMLHttpRequest进行跨域,所以利用Script DOM Element,将JSON填入一个回调函数中来实现其跨域,也就是JSONP(JSON with padding, 填充式JSON或参数式JSON)。
实际上JSONP就是将,得到JSON后回调的函数通过GET传参告诉服务器,然后服务器拼接一段脚本,用该回调函数并参数为需要的JSON数据,如:
callback({ "name": "Justany_WhiteSnow" });
jQuery团队当然希望开发者开发的时候,不需要想需不需要跨域,只要直接使用就行了。
所以他们把XMLHttpRequest、Script DOM Element、JSONP包装起来,都当成AJAX来使用。
这里顺便提一下,其实现代浏览器(Firefox 3.5+、Safari 4+、Chrome等)中,通过XMLHttpRequeest实现了CORS(Cross-Origin Resource Sharing, 跨源资源共享)原生支持。也就是XMLHttpRequest在某些浏览器中,实际上是可以跨域的,只需要设置一下HTTP Response Header中的Access-Control-Allow-Origin。比如设置成通配符*。
而IE8也引入XDomainRequest也实现了CORS。
但毕竟某些浏览器不行,所以,咳咳……这不能成为一种通用方案。
怎么包装起来?
首先我们有一个山寨XHR对象,也就是jqXHR对象。通过对其添加 send、 abort来模拟XHR对象。
可是我们需要在不同方案执行前先处理一下特异性的东东,所以我们需要一个预过滤机制( Prefilter )来预先处理一下。
然后我们需要知道到底应当用那一套方案来执行整个过程,所以我们需要一个分发机制( Transport )来得到最后的jqXHR对象。
inspectPrefiltersOrTransports
我们在jQuery.ajax找到了预过滤和分发机制的函数,inspectPrefiltersOrTransports。
// 预过滤 inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); // …… // 得到transport transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
然后我们看看这个函数在干些什么。
// 检测函数,预过滤或者分发器 function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { // 定义个参数对象 var inspected = {}, // 看看传进来的结构体是prefilters, 还是transports // 如果是prefilters,证明这是预过滤过程,如果是transports分发过程 // 所以这个是,是不是在分发过程 seekingTransport = ( structure === transports ); function inspect( dataType ) { var selected; // 将inspected的dataType对应属性设置成true inspected[ dataType ] = true ; // 遍历prefilters对应dataType对象中的所有过滤器或者转换器工厂函数 jQuery.each( structure[ dataType ] || [], function ( _, prefilterOrFactory ) { // 得到dataType或者转换器 var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); // 如果dataType为字符串,即上面过程是过滤器 // 如果在预过滤过程 // 并且这个过滤出来的dataType不等于刚开始传进来的dataType if ( typeof dataTypeOrTransport === "string" && !seekingTransport && ! inspected[ dataTypeOrTransport ] ) { // 将现在这个新的dataType插入到options中 options.dataTypes.unshift( dataTypeOrTransport ); // 检测新的dataType inspect( dataTypeOrTransport ); return false ; // 否则如果在分发过程 } else if ( seekingTransport ) { // 定义selected为dataTypeOrTransport return !( selected = dataTypeOrTransport ); } }); return selected; } // 检查dataTypes数组的第一个,如果结果是undefined, // 则看看上面检查的是不是通配符*,如果不是则检查通配符 return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); }
我们可以看到,这个函数实际上就是从prefilters和transports中取出对应dataType的东东,然后过滤或者分发。
那么怎么定义prefilters和transports这两个对象的呢?
jQuery.ajaxPrefilter & jQuery.ajaxTransport
实际上,jQuery是通过这两个接口来定义上面两个对象的内容的,一个是定义预过滤器,另一个则定义分发器。
jQuery.ajaxPrefilter =
addToPrefiltersOrTransports( prefilters );
jQuery.ajaxTransport
= addToPrefiltersOrTransports( transports );
而这两个方法都是由addToPrefiltersOrTransports生成的。
addToPrefiltersOrTransports
// jQuery.ajaxPrefilter和jQuery.ajaxTransport的构造函数 function addToPrefiltersOrTransports( structure ) { // dataTypeExpression是可选参数,缺省值为* return function ( dataTypeExpression, func ) { // 如果dataTypeExpression不是字符串 // 模拟重载 if ( typeof dataTypeExpression !== "string" ) { func = dataTypeExpression; // 缺省为* dataTypeExpression = "*" ; } var dataType, i = 0 , // 将dataTypeExpression转为小写,并用空白拆成数组 dataTypes = dataTypeExpression.toLowerCase().match( core_rnotwhite ) || []; // 如果func是一个函数 if ( jQuery.isFunction( func ) ) { // 遍历dataTypeExpression中的所有dataType while ( (dataType = dataTypes[i++ ]) ) { // 如果有必要则删除头部的+号 if ( dataType[0] === "+" ) { // 删除+号 dataType = dataType.slice( 1 ) || "*" ; // 往structure对应的dataType中推入func函数 (structure[ dataType ] = structure[ dataType ] || []).unshift( func ); // 否则不需要处理 } else { // 往structure对应的dataType中推入func函数 (structure[ dataType ] = structure[ dataType ] || []).push( func ); } } } }; }
接下来就是通过调用jQuery.ajaxPrefilter和jQuery.ajaxTransport方法,添加预过滤器和分发器来完成包装。