代码改变世界

jQuery ajax —— 将类AJAX方法包装起来

2013-03-26 08:03  Justany_WhiteSnow  阅读(4077)  评论(1编辑  收藏  举报

上一篇文章,将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方法,添加预过滤器和分发器来完成包装。