jQuery 1.9 Ajax代码带注释

/* -----------ajax模块开始 -----------*/
var
    // Document location
    ajaxLocParts,
    ajaxLocation,
    ajax_nonce = jQuery.now(),

    ajax_rquery = /\?/,
    rhash = /#.*$/,
    rts = /([?&])_=[^&]*/,
    rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL
    // #7653, #8125, #8152: local protocol detection
    rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/,
    rnoContent = /^(?:GET|HEAD)$/,
    rprotocol = /^\/\//,
    rurl = /^([\w.+-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,  //例如:["http://localhost:8080", "http:", "localhost", "8080"]

    // Keep a copy of the old load method
    //在ajax中会给jQuery原型定义load函数。 这里使用_load存储可能的之前就定义了的load函数。
    //jquery中,load函数有两种不同的用途。
    //$elem.load(fun)  load事件的监听器函数
    //$elem.load(url, params, callback )  通过ajax加载文档到当前元素下
    _load = jQuery.fn.load,

    /* Prefilters
     * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
     * 2) These are called:
     *    - BEFORE asking for a transport
     *    - AFTER param serialization (s.data is a string if s.processData is true)
     * 3) key is the dataType
     * 4) the catchall symbol "*" can be used
     * 5) execution will start with transport dataType and THEN continue down to "*" if needed
     */
    /*
    存储通过ajaxPrefilter函数添加的前置过滤函数;
    用途: 针对ajax设置的datatype,添加过滤函数; 例如对请求script的ajax,在序列化参数data之后,发送请求之前,对请求进行修改过滤操作。
    例:prefiters = {"script":[function(){},function(){}],"text":[function(){}],"*":[function(){}]}
    根据每次ajax请求的数据类型调用不同的函数队列,之后"*"对应的的队列也会被调用
    */

    prefilters  = {},

    /* Transports bindings
     * 1) key is the dataType
     * 2) the catchall symbol "*" can be used
     * 3) selection will start with transport dataType and THEN go to "*" if needed
     */
    // transports存储通过ajaxTransport函数添加的传输函数;  传输函数就是发送请求,返回结果这一过程的代理。
    //意味着你可以很灵活的针对某一ajax请求的数据类型使用自己方式来得到和处理数据
    //其结构同prefilters
    //"*"可以用来处理所有数据类型的请求
    transports = {},

    // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
    allTypes = "*/".concat("*");  //   "*/*"

// #8138, IE may throw an exception when accessing
// a field from window.location if document.domain has been set
//IE中读取location.href可能会出错。
try {
    ajaxLocation = location.href;
} catch( e ) {
    // Use the href attribute of an A element
    // since IE will modify it given document.location
    ajaxLocation = document.createElement( "a" );
    ajaxLocation.href = "";
    ajaxLocation = ajaxLocation.href;
}
// Segment location into parts
//把页面地址分解。
////例如:["http://localhost:8080", "http:", "localhost", "8080"]
ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];

// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
//structure参数可以是前面定义的prefilters或者transports用来存储的对象。
//返回一个函数。 如果structure参数传入的是prefiler对象,那么返回的函数被jQuery.ajaxPrefilter引用。
//如果参数是transport对象,那么返回的函数被jQuery.ajaxTransport引用。  (见后面的代码)
// ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
// ajaxTransport: addToPrefiltersOrTransports( transports ),
// jQuery.ajaxPrefilter 和 jQuery.ajaxTransport 函数的区别就在于,两者使用的存储对象不同。
function addToPrefiltersOrTransports( structure ) {
    // dataTypeExpression is optional and defaults to "*"
    //dataTypeExpression参数可选,默认为"*"
    //dataTypeExpression可以是空格分割的多个dataType。例:  "script json"
    return function( dataTypeExpression, func ) {
        //省略dataTypeExpression时
        //参数修正
        if ( typeof dataTypeExpression !== "string" ) {
            func = dataTypeExpression;
            dataTypeExpression = "*";
        }

        var dataType,
            i = 0,
             // "script json" --> ["script","json"]
            dataTypes = dataTypeExpression.toLowerCase().match( core_rnotwhite ) || []; //切割dataTypeExpression成数组

        if ( jQuery.isFunction( func ) ) {
            // For each dataType in the dataTypeExpression
            while ( (dataType = dataTypes[i++]) ) {
                // Prepend if requested
                //如果datatype以+开头,表示函数应该被插入到相应的调用函数队列头部
                if ( dataType[0] === "+" ) {  //string.charAt(0)
                    dataType = dataType.slice( 1 ) || "*";
                    //在存储对象中,每种dataType对应一个函数队列。
                    (structure[ dataType ] = structure[ dataType ] || []).unshift( func );

                // Otherwise append
                //函数被插入到相应的调用函数队列尾部
                } else {
                    (structure[ dataType ] = structure[ dataType ] || []).push( func );
                }
            }
        }
    };
}

// Base inspection function for prefilters and transports
//options:the request options   与ajaxSetting合并后的options
//originalOptions: ajax函数传入的options
//jqXHR: the jqXHR object of the request  
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 );
            //对于prefilter调用,如果上面函数返回的是代表datatype的字符串,并且此种datatype的前置过滤函数队列未调用过,那么跳转到执行此datatype的前置过滤函数队列
            if( typeof dataTypeOrTransport === "string" && !seekingTransport && !inspected[ dataTypeOrTransport ] ) {
                options.dataTypes.unshift( dataTypeOrTransport );//新的dataType添加到options.dataTypes头部
                inspect( dataTypeOrTransport ); //跳转到新的dataType队列
                return false; //返回false终止jQuery.each操作。终止当前dataType的前置过滤函数队列的调用。
            } else if ( seekingTransport ) { //对于Transport调用,dataTypeOrTransport变量应该是一个表示传输过程的对象。
                return !( selected = dataTypeOrTransport );   //返回false终止jQuery.each操作。selected变量指向这个对象。
            }
        });
        return selected;  //对于Transport调用,返回得到的传输对象或者undefined。对于prefilter调用,返回undefined
    }
    //如果dataType不是"*" ,调用inspect(dataType)后,继续调用inspect("*")
    //因为inspect函数对于prefilter和transport调用的返回值不一样,所有:
    //对于prefilter,先inspect(options.dataTypes[0]),再inspect(dataTypes["*"])
    //对于transport,先inspect(options.dataTypes[0]),如果得到传输对象则继续。否则检查"*"尝试得到传输对象。
    //    注意:只使用dataTypes[0]  
    //inspected数组用来防止重复调用,例如dataTypes[0] =="*"的情况。
    return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" );
}

// A special extend for ajax options
// that takes "flat" options (not to be deep extended)
// Fixes #9887
//jQuery.ajaxSettings.flatOptions中定义的的属性为浅扩展,其它属性为深扩展。
function ajaxExtend( target, src ) {
    var deep, key,
        flatOptions = jQuery.ajaxSettings.flatOptions || {};//不需要深扩展的属性的集合。
        //如果属性不需要深扩展,直接赋值给target
        //否则添加到deep对象中
    for ( key in src ) {
        if ( src[ key ] !== undefined ) {
            ( flatOptions[ key ] ? target : ( deep || (deep = {}) ) )[ key ] = src[ key ];
        }
    }
    if ( deep ) {
        jQuery.extend( true, target, deep );//jQuery.extend函数设置第一个参数deep为true,深扩展
    }

    return target;
}

jQuery.fn.load = function( url, params, callback ) {
    //如果第一个参数类型非string,那么此load函数调用的目的是为了绑定javascript load事件处理程序
    // $("#image").load(handler)
    if ( typeof url !== "string" && _load ) {  //_load是对先前定义的load函数的缓存。
        return _load.apply( this, arguments );
    }
    // url   -->  "tt/test.jsp #selector"
    var selector, response, type,
        self = this,
        off = url.indexOf(" ");

    if ( off >= 0 ) {
        selector = url.slice( off, url.length );
        url = url.slice( 0, off );
    }

    // If it's a function
    //修正参数
    if ( jQuery.isFunction( params ) ) {

        // We assume that it's the callback
        callback = params;
        params = undefined;

    // Otherwise, build a param string
    //如果params是一个对象,修改type为post
    } else if ( params && typeof params === "object" ) {
        type = "POST";
    }

    // If we have elements to modify, make the request
    //确保当前jQuery对象不是空集合,否则ajax请求毫无意义。
    if ( self.length > 0 ) {
        jQuery.ajax({
            url: url,
            // if "type" variable is undefined, then "GET" method will be used
            type: type,
            dataType: "html",
            data: params
        }).done(function( responseText ) {

            // Save response for use in complete callback
            response = arguments;

            self.html( selector ?

                // If a selector was specified, locate the right elements in a dummy div
                // Exclude scripts to avoid IE 'Permission Denied' errors
                jQuery("<div>").append( jQuery.parseHTML( responseText ) ).find( selector ) :

                // Otherwise use the full result
                responseText );

        }).complete( callback && function( jqXHR, status ) {
            //在jQuery对象上调用each方法。 
            //each第二个参数是一个数组或者伪数组如arguments,此时数组中的元素就是是遍历过程中每次传递给callback的参数。
            self.each( callback, response || [ jqXHR.responseText, status, jqXHR ] );
        });
    }

    return this;
};

// Attach a bunch of functions for handling common AJAX events
//创建用于绑定全局ajax事件处理器的系列函数
//$(document).ajaxStart(callBack);
jQuery.each( [ "ajaxStart", "ajaxStop", "ajaxComplete", "ajaxError", "ajaxSuccess", "ajaxSend" ], function( i, type ){
    jQuery.fn[ type ] = function( fn ){
        return this.on( type, fn );  //通过on函数绑定对应的事件处理器
    };
});
//get和post快捷函数定义
jQuery.each( [ "get", "post" ], function( i, method ) {
    jQuery[ method ] = function( url, data, callback, type ) {
        // shift arguments if data argument was omitted
        //修正参数,如果省略了data参数
        if ( jQuery.isFunction( data ) ) {
            type = type || callback;
            callback = data;
            data = undefined;
        }

        return jQuery.ajax({
            url: url,
            type: method,
            dataType: type,
            data: data,
            success: callback
        });
    };
});

jQuery.extend({

    // Counter for holding the number of active queries
    //此时存在的其它未完成的ajax请求数
    active: 0,

    // Last-Modified header cache for next request
    lastModified: {},
    etag: {},
    //默认ajax设置
    ajaxSettings: {
        url: ajaxLocation,  //默认url为当前文档地址
        type: "GET", //默认get方式
        isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),  //是否是本地文件系统 如:浏览器地址以file:///开头
        global: true, //是否支持全局ajax事件
        processData: true, //是否把data选项值处理成字符串形式。
        async: true,//是否异步
        contentType: "application/x-www-form-urlencoded; charset=UTF-8", //请求头中默认的contentType
        /*
        timeout: 0,
        data: null,
        dataType: null,
        username: null,
        password: null,
        cache: null,
        throws: false,  //设为true时转换错误时将错误throw
        traditional: false,
        headers: {},
        */
         
        //如果Ajax请求未设置具体的dataType
        //jQuery通过这个对象,根据使用正则来匹配响应头的content-type值,对应于正则的属性名就被认为是返回内容的数据类型。
        contents: {
            xml: /xml/,
            html: /html/,
            json: /json/
        },
        //响应对象中的字段到jqXHR对象中字段的映射
        responseFields: {
            xml: "responseXML",
            text: "responseText"
        },

        // Data converters
        // Keys separate source (or catchall "*") and destination types with a single space
        //数据转换工具 
        //例如 "text json": jQuery.parseJSON  意思就是可以通过jQuery.parseJSON函数,将text类型的数据转换为json类型的数据
        converters: {

            // Convert anything to text
            "* text": window.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
        },

        // For options that shouldn't be deep extended:
        // you can add your own custom options here if
        // and when you create one that shouldn't be
        // deep extended (see ajaxExtend)
        //用来设置那些不应该被深扩展的属性
        //ajaxExtend中用到
        flatOptions: {
            url: true,
            context: true
        }
    },

    // Creates a full fledged settings object into target
    // with both ajaxSettings and settings fields.
    // If target is omitted, writes into ajaxSettings.
    //如果调用时只传入一个参数,那么扩展的目标对象是ajaxSettings。 (jQuery用户这样调用来扩展全局默认Ajax设置,所有的ajax请求都会受此影响)
    //否则使用setting(jQuery用户设置)和ajaxSettings(默认全局设置)一起扩展到target(目标)对象 (jQuery内部调用)
    ajaxSetup: function( target, settings ) {
        return settings ?

            // Building a settings object
            ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :

            // Extending ajaxSettings
            ajaxExtend( jQuery.ajaxSettings, target );
    },
    //定义ajaxPrefilter和ajaxTransport方法
    ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
    ajaxTransport: addToPrefiltersOrTransports( transports ),

    // Main method
    // 调用方式 jQuery.ajax(url[,options])或者jQuery.ajax([options])
    /*
    *@param options 用户设置的选项,用来配置ajax
    */
    ajax: function( url, options ) {

        // If url is an object, simulate pre-1.5 signature
        //参数修正。
        if ( typeof url === "object" ) {
            options = url;
            url = undefined;
        }

        // Force options to be an object
        options = options || {};

        var // Cross-domain detection vars
            parts,
            // Loop variable
            i,
            // URL without anti-cache param
            cacheURL,
            // Response headers as string
            responseHeadersString,
            // timeout handle

            timeoutTimer,

            // To know if global events are to be dispatched
            fireGlobals,

            transport,
            // Response headers
            responseHeaders,
            // Create the final options object
            // ajaxSetting 和options都扩展到{}中
            //s是默认设置与用户设置的选项两者扩展后的对象,综合了用户选项和默认设置。
            s = jQuery.ajaxSetup( {}, options ),
            // Callbacks context
            //如果用户选项没有设置context,那么callbackContext的值默认为s对象
            callbackContext = s.context || s,
            // Context for global events is callbackContext if it is a DOM node or jQuery collection
            //如果设置的context是一个DOM元素或者jQuery对象,则设置globalEventContext值为此context构建的jquery对象
            //否则否则globalEventContext值为jQuery.event对象
            globalEventContext = s.context && ( callbackContext.nodeType || callbackContext.jquery ) ?
                jQuery( callbackContext ) :
                jQuery.event,
            // Deferreds
            deferred = jQuery.Deferred(), 
            //jQuery.Callbacks方法构造一个"once memory"的回调队列
            completeDeferred = jQuery.Callbacks("once memory"), 
            // Status-dependent callbacks
            //用户设置的status选项
            //key为status ,value为函数集合
            //根据状态码设置回调函数 (不同的状态码对应不同的函数列表)
            statusCode = s.statusCode || {},
            // Headers (they are sent all at once)
            requestHeaders = {},
            requestHeadersNames = {},
            // The jqXHR state
            state = 0,
            // Default abort message
            strAbort = "canceled",
            // Fake xhr
            //JjQuery封装的jqXHR对象
            jqXHR = {
                readyState: 0,

                // Builds headers hashtable if needed
                getResponseHeader: function( key ) {
                    var match;
                    if ( state === 2 ) { // state==2代表http请求数据过程完成
                        if ( !responseHeaders ) { // responseHeadersString还未转换到responseHeaders。 转换之。
                            responseHeaders = {};
                            while ( (match = rheaders.exec( responseHeadersString )) ) { //正则有g和m,每行匹配一次
                                responseHeaders[ match[1].toLowerCase() ] = match[ 2 ];
                            }
                        }
                        match = responseHeaders[ key.toLowerCase() ];
                    }
                    //将undefined转换为null
                    //返回null或者字符串。
                    return match == null ? null : match;
                },

                // Raw string
                getAllResponseHeaders: function() { 
                    return state === 2 ? responseHeadersString : null;
                },

                // Caches the header
                //requestHeadersNames中缓存name的小写形式到的映射name      -->  key is  lname , value is name
                //requestHeaders中缓存name到value的映射  --> key is name, value is value
                setRequestHeader: function( name, value ) {
                    var lname = name.toLowerCase();
                    if ( !state ) { //state== 0 代表http请求过程还未开始。
                        //将name值在requestHeadersNames中作为属性名为小写形式lname的值缓存
                        //key  is lname,value is name;
                        name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;
                        //set
                        requestHeaders[ name ] = value;
                    }
                    return this;
                },

                // Overrides response content-type header
                //用来设置s.mimeType
                overrideMimeType: function( type ) {
                    if ( !state ) {
                        s.mimeType = type;
                    }
                    return this;
                },

                // Status-dependent callbacks
                //当前jqXHR状态不同,函数的用途不同。
                //jqXHR完成状态时,根据其状态码调用回调函数。
                //否则添加回调函数。
                statusCode: function( map ) {
                    var code;
                    if ( map ) {
                        if ( state < 2 ) {//给statusCode添加回调函数,前提是Ajax请求此时是未完成状态
                            for ( code in map ) {
                                // Lazy-add the new callback in a way that preserves old ones
                                //statusCode[ code ]可能已经设置过。 已经是一个函数或一个函数数组
                                //新加入的函数或者函数数组放在数组的新建数组的尾部 ,最后触发的时候相当于在时间上后于以前加入的函数处理
                                // 最后调用的时候,这个多层数组最后会使用Callbacks.add函数添加,这个函数对此进行了处理。
                                statusCode[ code ] = [ statusCode[ code ], map[ code ] ];
                            }
                        } else { //请求已经响应,直接执行对应的回调
                            // Execute the appropriate callbacks
                            jqXHR.always( map[ jqXHR.status ] );
                        }
                    }
                    return this;
                },

                // Cancel the request
                //用来取消ajax请求
                abort: function( statusText ) {
                    var finalText = statusText || strAbort;
                    if ( transport ) { //调用传输对象的abort方法,终止传输
                        transport.abort( finalText );
                    }
                    done( 0, finalText );
                    return this;
                }
            };

        // Attach deferreds
        //通过deferred对象得到的promise对象
        //jqXHR对象继承promise对象,并添加complete方法,该方法引用了completeDeferred.add方法
        deferred.promise( jqXHR ).complete = completeDeferred.add;
        jqXHR.success = jqXHR.done;
        jqXHR.error = jqXHR.fail;

        // Remove hash character (#7531: and string promotion)
        // Add protocol if not provided (#5866: IE7 issue with protocol-less urls)
        // Handle falsy url in the settings object (#10093: consistency with old signature)
        // We also use the url parameter if available
        //移除url中的hash字符串
        //如果url以//开头,则添加协议名。(IE7的问题);
        s.url = ( ( url || s.url || ajaxLocation ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" );

        // Alias method option to type as per ticket #12004
        //method 作为 type的别名
        s.type = options.method || options.type || s.method || s.type;

        // Extract dataTypes list
        //dataTypes可以是以空格分隔的字符串,包含多个dataType。默认为“*”  例如: "text xml" 表示将text的响应当成xml对待
        // 转换成数组   "text xml"  -->  ["text","xml"]
        s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().match( core_rnotwhite ) || [""];

        // A cross-domain request is in order when we have a protocol:host:port mismatch
        // 非跨域请求需要满足 协议名 主机名 端口都匹配
        if ( s.crossDomain == null ) {
            parts = rurl.exec( s.url.toLowerCase() );
            //比较协议名,域名,端口号,三者任一不同,则为跨域。
            s.crossDomain = !!( parts &&
                ( parts[ 1 ] !== ajaxLocParts[ 1 ] || parts[ 2 ] !== ajaxLocParts[ 2 ] || 
                    ( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) !=  //如果无端口号,那么对于http协议默认是80,否则为443(主要用于https)
                        ( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? 80 : 443 ) ) )
            );
        }

        // Convert data if not already a string
        //s.processData 默认为true ,即data选项(请求数据)默认被转换成字符串形式。 值为false时不转换data属性值。
        //通过jQuery.param函数来转换
        if ( s.data && s.processData && typeof s.data !== "string" ) {
            s.data = jQuery.param( s.data, s.traditional ); //s.traditional 可以设置转换是否使用传统模式
        }

        // Apply prefilters
        //应用前置过滤
        inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
        //如果在prefilters的处理函数中调用了jqXHR的abort函数,state会被设置为2;
        // If request was aborted inside a prefilter, stop there
        if ( state === 2 ) {
            return jqXHR;
        }

        // We can fire global events as of now if asked to
        //是否触发ajax全局事件标志
        fireGlobals = s.global;

        // Watch for a new set of requests
        //global标志为true时,当前不存在其它未完成的ajax请求,触发ajaxStart事件。 
        // jQuery.active记录未完成的ajax请求数量
        if ( fireGlobals && jQuery.active++ === 0 ) {
            //jQuery.event.triggerr函数在调用时如果第三个参数elem为空时,默认是在document上触发。
            jQuery.event.trigger("ajaxStart");
        }

        // Uppercase the type
        s.type = s.type.toUpperCase();

        // Determine if request has content
        // post请求有请求体,即数据通过请求体发送,而不是通过url
        s.hasContent = !rnoContent.test( s.type );

        // Save the URL in case we're toying with the If-Modified-Since
        // and/or If-None-Match header later on
        cacheURL = s.url;

        // More options handling for requests with no content
        //get请求
        if ( !s.hasContent ) {

            // If data is available, append data to url
            // 将data数据添加到url中
            if ( s.data ) {
                cacheURL = ( s.url += ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) + s.data );
                // #9682: remove data so that it's not used in an eventual retry
                delete s.data; //删除data
            }

            // Add anti-cache in url if needed
            //如果设置不要缓存,在url中加入一个_参数,其值为随机数。以此来破坏浏览器缓存机制
            if ( s.cache === false ) {
                s.url = rts.test( cacheURL ) ?

                    // If there is already a '_' parameter, set its value
                    //如果参数中已经存在一个参数名"_",覆盖它的值。
                    cacheURL.replace( rts, "$1_=" + ajax_nonce++ ) :

                    // Otherwise add one to the end
                    cacheURL + ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ajax_nonce++;
            }
        }

        // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
        //设置s.ifModified为true后,如果服务器的内容未改变,那么服务器会返回不带数据的304报文。数据直接在缓存中得到
        //lastModified和etag同时使用
        if ( s.ifModified ) {
            if ( jQuery.lastModified[ cacheURL ] ) {  //在jquery的lastModified缓存中查找当前url.
                jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] );
            }
            if ( jQuery.etag[ cacheURL ] ) {
                jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] );
            }
        }

        // Set the correct header, if data is being sent
        if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
            jqXHR.setRequestHeader( "Content-Type", s.contentType );
        }

        // Set the Accepts header for the server, depending on the dataType
        //学习学习 
        // Accept-Language: fr; q=1.0, en; q=0.5   法语和英语都可以 最好是法语
        // Accept: text/html; q=1.0, text; q=0.8, image/gif; q=0.6, image/jpeg; q=0.6, image/*; q=0.5, *; q=0.1
        //逗号分隔不同的选项,分号后面的代表优先级。     
        jqXHR.setRequestHeader(
            "Accept",
            s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?  
                // "*/*"表示任意类型,分号后面的q=0.01表示优先级啦。
                //多个类型直接用分号隔开
                s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
                s.accepts[ "*" ]
        );

        // Check for headers option
        //s.headers对象中的元素复制到RequestHeaders中
        for ( i in s.headers ) {
            jqXHR.setRequestHeader( i, s.headers[ i ] );
        }

        // Allow custom headers/mimetypes and early abort
        //beforeSend事件绑定的函数如果返回false或者在函数中设置state为2,那么调用jqXHR.abort()方法,终止请求
        //如果在jqXHR中也调用了abort方法,那么肯定会导致abort方法中的transport.abort方法再次执行,这样不会有问题么。。。
        if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
            // Abort if not done already and return
            return jqXHR.abort();
        }

        // aborting is no longer a cancellation
        strAbort = "abort";

        // Install callbacks on deferreds
        //将options中的success,error ,complete属性方法使用jqXHR对应的监听器注册。
        //    如: jqXHR["success"](callBack);
        for ( i in { success: 1, error: 1, complete: 1 } ) {
            jqXHR[ i ]( s[ i ] );
        }

        // Get transport
        //获取传输对象。
        transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );

        // If no transport, we auto-abort
        if ( !transport ) {
            done( -1, "No Transport" );
        } else {
            jqXHR.readyState = 1;  //找到transport后,jqXHR.readyState变为1,标志jqXHR开始

            // Send global event
            //触发全局ajaxSend事件
            if ( fireGlobals ) {
                globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
            }
            // Timeout
            //异步模式下,timeout设置最大等待时间
            if ( s.async && s.timeout > 0 ) {
                timeoutTimer = setTimeout(function() {
                    jqXHR.abort("timeout");
                }, s.timeout );
            }

            try {
                state = 1;  //state设置为1,标志传输过程开始
                //调用transport的send方法,传入请求头和回调函数
                //注意回调函数时done函数
                transport.send( requestHeaders, done );
            } catch ( e ) {
                // Propagate exception as error if not done
                if ( state < 2 ) {
                    done( -1, e );
                // Simply rethrow otherwise
                } else {
                    throw e;
                }
            }
        }

        // Callback for when everything is done
            //transport.send完成后的回调函数,或者出错时手动调用
        //四个参数 
        //status 和 statusText    例如  2 "success"
        //responses   对象   例如   {xml:"someString",html:"someString"}
        //headers 包含所有响应头信息的字符串 
        function done( status, nativeStatusText, responses, headers ) {
            var isSuccess, success, error, response, modified,
                statusText = nativeStatusText;

            // Called once
            //已经调用过done了
            if ( state === 2 ) {
                return;
            }

            // State is "done" now
            //state = 2意味着传输过程完成
            state = 2;

            // Clear timeout if it exists
            //清除定时任务
            if ( timeoutTimer ) {
                clearTimeout( timeoutTimer );
            }

            // Dereference transport for early garbage collection
            // (no matter how long the jqXHR object will be used)
            //清除transport传输对象
            transport = undefined;

            // Cache response headers
            //headers复制给responseHeadersString
            responseHeadersString = headers || "";

            // Set readyState
            //设置readyState
            //status>0时 jqXHR.readyState设置为4,标志jqXHR过程成功完成。
            //jqXHR.readyState设置为0时,表示jqXHR过程失败
            jqXHR.readyState = status > 0 ? 4 : 0;

            // Get response data
            //调用ajaxHandleResponses处理response
            if ( responses ) {
                response = ajaxHandleResponses( s, jqXHR, responses );
            }

            // If successful, handle type chaining
            //success
            if ( status >= 200 && status < 300 || status === 304 ) {

                // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
                if ( s.ifModified ) {
                    modified = jqXHR.getResponseHeader("Last-Modified");
                    if ( modified ) {
                        //缓存当前url的最近修改时间
                        jQuery.lastModified[ cacheURL ] = modified;
                    }
                    modified = jqXHR.getResponseHeader("etag");
                    if ( modified ) {
                        //缓存etag值
                        jQuery.etag[ cacheURL ] = modified;
                    }
                }

                // if no content
                // 204   no content
                if ( status === 204 ) {
                    isSuccess = true;
                    statusText = "nocontent";

                // if not modified
                // 304 notmodified
                } else if ( status === 304 ) {  //返回304的话,怎么从缓存中拿到数据?
                    isSuccess = true;
                    statusText = "notmodified";

                // If we have data, let's convert it
                // ajaxConvert
                //否则就是得到数据的情况了。
                } else {
                    isSuccess = ajaxConvert( s, response );
                    statusText = isSuccess.state;
                    success = isSuccess.data;
                    error = isSuccess.error;
                    isSuccess = !error;
                }
            } else { //failed
                // We extract error from statusText
                // then normalize statusText and status for non-aborts
                error = statusText;
                if ( status || !statusText ) {
                    statusText = "error";
                    if ( status < 0 ) {
                        status = 0;
                    }
                }
            }

            // Set data for the fake xhr object
            jqXHR.status = status;
            //优先使用参数传入的nativeStatusText
            jqXHR.statusText = ( nativeStatusText || statusText ) + "";

            // Success/Error
            //触发success 或者 error
            if ( isSuccess ) {
                deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
            } else {
                deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
            }

            // Status-dependent callbacks
            //根据当前响应的状态码触发options选项statusCode属性对象中对应的函数
            jqXHR.statusCode( statusCode );
            statusCode = undefined;
            //如果没给定context,那么调用jQuery.event.trigger函数触发这两个事件,此时默认context为document
            //否则在context上触发  ajaxSuccess 或者 ajaxError
            if ( fireGlobals ) {
                globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",
                    [ jqXHR, s, isSuccess ? success : error ] );
            }

            // Complete
            //触发当前ajax通过complete选项绑定的回调函数
            completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
            //触发全局绑定的ajaxComplete 
            // 所有的ajax请求都执行完了就触发"ajaxStop"
            if ( fireGlobals ) {
                globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
                // Handle the global AJAX counter
                //--jQuery.active
                if ( !( --jQuery.active ) ) {  //所有的ajax请求都执行完了就触发ajaxStop
                    jQuery.event.trigger("ajaxStop");
                }
            }
        }
        //返回jqXHR对象
        return jqXHR;
    },

    getScript: function( url, callback ) {
        return jQuery.get( url, undefined, callback, "script" );
    },

    getJSON: function( url, data, callback ) {
        return jQuery.get( url, data, callback, "json" );
    }
});

/* Handles responses to an ajax request:
 * - sets all responseXXX fields accordingly
 * - finds the right dataType (mediates between content-type and expected dataType)
 * - returns the corresponding response
 */
 /*设置jqXHR的responseXXX属性
 找到正确的dataType(介于responses中dataType与期待的dataType可以直接转换的类型),并且添加到dataTypes中
 返回响应内容*/
 //这里需要距离。  因为responses中可能含有多种类型dataType的值,比如xml或者html,对着两种类型都尝试是否能直接转换成期待的dataType
 //responses  可能-->   {xml:"somestring",html:"someString"}
 //把responses中的类型添加到dataTypes中,优先添加能直接转换的,否则添加第一个属性。如果添加的type和dataTypes[0]重合则不需要添加。
  //返回responses中对应添加类型的值。
function ajaxHandleResponses( s, jqXHR, responses ) {
    var firstDataType, ct, finalDataType, type,
        contents = s.contents,  //contents选项
/*contents: {
            xml: /xml/,
            html: /html/,
            json: /json/
        }*/
        dataTypes = s.dataTypes,
        responseFields = s.responseFields;
        /*responseFields: {
            xml: "responseXML",
            text: "responseText"
        }*/
        //responseFields 包含response中需要转换到jqXHR中的字段.  key为response中需要转换的属性名  value为转换到jqXHR中的属性名  
    for ( type in responseFields ) {
        if ( type in responses ) {
            jqXHR[ responseFields[type] ] = responses[ type ];  //例如:  jqXHR["responseXML"] = responses["xml"]
        }
    }

    // Remove auto dataType and get content-type in the process
    //将dataTypes数组前面的所有"*"移除
    //dataTypes前面必须是一个"*",ct才会被赋值
    while( dataTypes[ 0 ] === "*" ) {
        dataTypes.shift();
        if ( ct === undefined ) {//初始化ct为content-type
            ct = s.mimeType || jqXHR.getResponseHeader("Content-Type"); //s.mimeType选项值覆盖响应的content-type
        }
    }

    // Check if we're dealing with a known content-type
    //对于上面: ct未赋值的情况,说明dataTypes[0]!="*" 那么默认dataTypes[0] 就是响应头content-type在contents中对应的属性名
    //否则以响应的content-type对应的type作为datatypes的第一个元素
    if ( ct ) {
        for ( type in contents ) {//遍历contents
            if ( contents[ type ] && contents[ type ].test( ct ) ) { //执行type对应的正则来匹配响应头中的content-type
                dataTypes.unshift( type ); //匹配到的type添加到dataTypes数组前面
                break;
            }
        }
    }

    // Check to see if we have a response for the expected dataType
    if ( dataTypes[ 0 ] in responses ) {
        finalDataType = dataTypes[ 0 ];
    } else {
        // Try convertible dataTypes
        //否则,因为response可能包含多个属性,对每个属性都尝试是否可以直接转换(通过检查s.converters)
        for ( type in responses ) {
            //直接可以转换或者dataTypes为空时
            if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) {
                finalDataType = type; 
                break;
            }
            if ( !firstDataType ) { //responses中第一个不能转换的type
                firstDataType = type;
            }
        }
        // Or just use first one
        //找不到可以直接转换的类型,那么finalDataType就是responses中第一个不能转换的type
        finalDataType = finalDataType || firstDataType;
    }

    // If we found a dataType
    // We add the dataType to the list if needed
    // and return the corresponding response
    if ( finalDataType ) {

        if ( finalDataType !== dataTypes[ 0 ] ) { //如果是中间type。
            dataTypes.unshift( finalDataType );//添加到dataTypes前面
        }
        return responses[ finalDataType ];//返回response中对应finalDataType类型的数据
    }
}

// Chain conversions given the request and the original response
//链式转换。
//将response转换按照dataTypes中的类型依次转换,最后返回一个封装后的结果。  成功时:{ state: "success", data: response };
function ajaxConvert( s, response ) {
    var conv2, current, conv, tmp,
        converters = {},
        i = 0,
        // Work with a copy of dataTypes in case we need to modify it for conversion
        //复制s.dataTypes,通过调用s.dataTypes.slice();
        dataTypes = s.dataTypes.slice(),
        prev = dataTypes[ 0 ];

    // Apply the dataFilter if provided
    //dataFilter方法先于类型转换。
    if ( s.dataFilter ) {
        response = s.dataFilter( response, s.dataType );
    }

    // Create converters map with lowercased keys
    if ( dataTypes[ 1 ] ) {
        for ( conv in s.converters ) {
            converters[ conv.toLowerCase() ] = s.converters[ conv ];
        }
    }

    // Convert to each sequential dataType, tolerating list modification
    //循环dataTypes中相邻的两个元素,判断其是否可以直接转换。
    //如果不能直接转换,那么尝试曲线救国 即A-->C行不通    找看看A-->B-->C
    for ( ; (current = dataTypes[++i]); ) {

        // There's only work to do if current dataType is non-auto
        if ( current !== "*" ) {

            // Convert response if prev dataType is non-auto and differs from current
            if ( prev !== "*" && prev !== current ) {

                // Seek a direct converter
                conv = converters[ prev + " " + current ] || converters[ "* " + current ];

                // If none found, seek a pair
                //如果不能直接转换
                if ( !conv ) {
                    //遍历converters
                    for ( conv2 in converters ) {

                        // If conv2 outputs current
                        tmp = conv2.split(" ");
                        if ( tmp[ 1 ] === current ) {

                            // If prev can be converted to accepted input
                            conv = converters[ prev + " " + tmp[ 0 ] ] ||
                                converters[ "* " + tmp[ 0 ] ];
                            if ( conv ) {
                                // Condense equivalence converters
                                if ( conv === true ) {
                                    conv = converters[ conv2 ];

                                // Otherwise, insert the intermediate dataType
                                //这里不需要判断converters[ conv2 ] === true的情况。
                                //因为在这种情况下conv的值已经是转换函数了。
                                //如果converters[ conv2 ] !== true,将找到的可以用来作过渡转换的type添加到dataTypes中合适的位置
                                } else if ( converters[ conv2 ] !== true ) {
                                    current = tmp[ 0 ];

                                    dataTypes.splice( i--, 0, current );
                                }

                                break;  //找到了就中断循环
                            }
                        }
                    }
                }

                // 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 );
                        } catch ( e ) {
                            return { state: "parsererror", error: conv ? e : "No conversion from " + prev + " to " + current };
                        }
                    }
                }
            }

            // Update prev for next iteration
            prev = current;
        }
    }
    //转换完成
    return { state: "success", data: response };
}
// Install script dataType
//扩展jQuery.ajaxSetting对象
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;
        }
    }
});

// Handle cache's special case and global
//script类型请求的前置处理
//a.默认不使用浏览器缓存
//b.对于跨域请求:使用get方法,并且设置global为false,即不触发全局ajax对象。
jQuery.ajaxPrefilter( "script", function( s ) {
    if ( s.cache === undefined ) {
        s.cache = false;
    }
    if ( s.crossDomain ) {
        s.type = "GET";
        s.global = false;
    }
});

// Bind script tag hack transport
//请求script文件使用的传输对象。
jQuery.ajaxTransport( "script", function(s) {

    // This transport only deals with cross domain requests
    //只处理跨域的部分
    //可以看到跨域的script文件请求通过新建script标签完成。
    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 ) {
                    script.charset = s.scriptCharset;
                }

                script.src = s.url;

                // Attach handlers for all browsers
                //isAbort参数在下面定义的abort方法中手动调用script.onload函数时设为true
                //IE的 script 元素支持onreadystatechange事件,不支持onload事件。
                //FF的script 元素不支持onreadystatechange事件,只支持onload事件。
                script.onload = script.onreadystatechange = function( _, isAbort ) {
                    //isAbort时,做清除script的处理
                    //!script.readyState 说明是在FF下面,此时表明load完成
                    ///loaded|complete/.test( script.readyState )表明在IE下需要检测到readyState为loaded或者complete时,才算load完成
                    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 );
                }
            }
        };
    }
});
var oldCallbacks = [], //回调函数名的回收站
    rjsonp = /(=)\?(?=&|$)|\?\?/;    // ?= 是正向先行断言

// Default jsonp settings
jQuery.ajaxSetup({
    jsonp: "callback",
    jsonpCallback: function() {
        //回收站里没得时就新建一个随机函数名
        var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( ajax_nonce++ ) );
        this[ callback ] = true; //this指向s
        return callback;
    }
});

// Detect, normalize options and install callbacks for jsonp requests
//对json和jsonp类型ajax请求的前置处理
jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {

    var callbackName, overwritten, responseContainer,
        /*
        先在s.url中寻找jsonp标志'anyCallbackName=?',如果未找到那么尝试在s.data中找标志"anyCallbackName=?" 。  "anyCallbackName"是用于设置回调的参数名,和服务器的设置相关。
        对于get请求,s.data字符串已经被添加到了s.url中,所以如果在s.url中未找到而在s.data中找到了那么一定是post请求。
        jsonProp  -->   false||"url"||"data"
        */
        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"
        );
    /*对于jsonp请求,可以通过在s.url或者s.data字符串中添加
        "anyCallbackName=?" 或者设置s.jsonp来告诉jQuery这是一jsonp请求。
        s.jsonp选项设置的是服务器相关的jsonp参数名。
        s.jsonpCallback参数可以是函数名字符串或者一个返回函数名字符串的函数。
            推荐是不手动设置此参数,通过jQuery随机生成(注意:手动设置函数名后,如果用户定义了同名函数,jQuery最终也会调用这个函数)。
    */
    // 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
        //s.jsonpCallback 如果是一个函数就取得函数返回值作为回调函数名,否则直接作为回调函数名
        callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ?
            s.jsonpCallback() :
            s.jsonpCallback;

        // Insert callback into url or form data
        //插入callback到url或者data中
        if ( jsonProp ) {//s.url或s.data中插入了"anyCallbackName=?"标志
            s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName );
        } else if ( s.jsonp !== false ) {//其它情况,即s.url和s.data中都没有手动插入"fun=?"标志,那么自动生成。
            s.url += ( ajax_rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName;
        }

        // Use data converter to retrieve json after script execution
        //设置script类型到json类型的转换
        //  因为当前函数最后会  return "script";
        s.converters["script json"] = function() {
            if ( !responseContainer ) {
                jQuery.error( callbackName + " was not called" );
            }
            return responseContainer[ 0 ];
        };

        // force json dataType
        //强制dataType[0] 为"json" .  意味着"jsonp"  也被设置为"json"
        s.dataTypes[ 0 ] = "json";

        // 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
        //委派到script类型
        return "script";
    }
});
var xhrCallbacks, xhrSupported,
    xhrId = 0,
    // #5280: Internet Explorer will keep connections alive if we don't abort on unload
    xhrOnUnloadAbort = window.ActiveXObject && function() {
        // Abort all pending requests
        var key;
        for ( key in xhrCallbacks ) {
            xhrCallbacks[ key ]( undefined, true );
        }
    };

// Functions to create xhrs
function createStandardXHR() {
    try {
        return new window.XMLHttpRequest();
    } catch( e ) {}
}

function createActiveXHR() {
    try {
        return new window.ActiveXObject("Microsoft.XMLHTTP");
    } catch( e ) {}
}

// Create the request object
// (This is still attached to ajaxSettings for backward compatibility 向后兼容)
jQuery.ajaxSettings.xhr = window.ActiveXObject ?
    /* Microsoft failed to properly
     * implement the XMLHttpRequest in IE7 (can't request local files),
     * so we use the ActiveXObject when it is available
     * Additionally XMLHttpRequest can be disabled in IE7/IE8 so
     * we need a fallback.
     */
     //在IE下面,ajax不能请求本地文件。
    function() {
        return !this.isLocal && createStandardXHR() || createActiveXHR();
    } :
    // For all other browsers, use the standard XMLHttpRequest object
    createStandardXHR;

// Determine support properties
xhrSupported = jQuery.ajaxSettings.xhr();
jQuery.support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported );
xhrSupported = jQuery.support.ajax = !!xhrSupported;

// Create transport if the browser can provide an xhr
if ( xhrSupported ) {

    jQuery.ajaxTransport(function( s ) {  //创建"*"对应的transport,即默认处理所有请求的transport
        // Cross domain only allowed if supported through XMLHttpRequest
        //跨域请求需要支持withCredentials属性的浏览器
        if ( !s.crossDomain || jQuery.support.cors ) {

            var callback;

            return {
                send: function( headers, complete ) {

                    // Get a new xhr
                    var handle, i,
                        xhr = s.xhr();

                    // Open the socket
                    // Passing null username, generates a login popup on Opera (#2865)
                    if ( s.username ) {
                        xhr.open( s.type, s.url, s.async, s.username, s.password );
                    } else {
                        xhr.open( s.type, s.url, s.async );
                    }

                    // Apply custom fields if provided
                    /*
                    例如:xhrFields: {
                      withCredentials: true
                       }
                       用来设置xhr请求的属性。
                   */
                    if ( s.xhrFields ) {
                        for ( i in s.xhrFields ) {
                            xhr[ i ] = s.xhrFields[ i ];
                        }
                    }

                    // Override mime type if needed
                    if ( s.mimeType && xhr.overrideMimeType ) {
                        xhr.overrideMimeType( s.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.
                    if ( !s.crossDomain && !headers["X-Requested-With"] ) {
                        headers["X-Requested-With"] = "XMLHttpRequest";
                    }

                    // Need an extra try/catch for cross domain requests in Firefox 3
                    try {
                        for ( i in headers ) {
                            xhr.setRequestHeader( i, headers[ i ] );
                        }
                    } catch( err ) {}

                    // Do send the request
                    // This may raise an exception which is actually
                    // handled in jQuery.ajax (so no try/catch here)
                    xhr.send( ( s.hasContent && s.data ) || null );

                    // Listener
                    callback = function( _, isAbort ) {
                        var status, responseHeaders, statusText, responses;

                        // Firefox throws exceptions when accessing properties
                        // of an xhr when a network error occurred
                        // http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE)
                        try {

                            // Was never called and is aborted or complete
                            if ( callback && ( isAbort || xhr.readyState === 4 ) ) {

                                // Only called once
                                callback = undefined;

                                // Do not keep as active anymore
                                if ( handle ) {
                                    xhr.onreadystatechange = jQuery.noop;
                                    if ( xhrOnUnloadAbort ) {
                                        delete xhrCallbacks[ handle ];
                                    }
                                }

                                // If it's an abort
                                if ( isAbort ) {
                                    // Abort it manually if needed
                                    if ( xhr.readyState !== 4 ) {
                                        xhr.abort();
                                    }
                                } else {
                                    responses = {};
                                    status = xhr.status;
                                    responseHeaders = xhr.getAllResponseHeaders();

                                    // When requesting binary data, IE6-9 will throw an exception
                                    // on any attempt to access responseText (#11426)
                                    if ( typeof xhr.responseText === "string" ) {
                                        responses.text = xhr.responseText;
                                    }

                                    // 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)
                                    if ( !status && s.isLocal && !s.crossDomain ) {
                                        status = responses.text ? 200 : 404;
                                    // IE - #1450: sometimes returns 1223 when it should be 204
                                    } else if ( status === 1223 ) {
                                        status = 204;
                                    }
                                }
                            }
                        } catch( firefoxAccessException ) {
                            if ( !isAbort ) {
                                complete( -1, firefoxAccessException );
                            }
                        }

                        // Call complete if needed
                        if ( responses ) {
                            complete( status, statusText, responses, responseHeaders );
                        }
                    };

                    if ( !s.async ) {
                        // if we're in sync mode we fire the callback
                        callback();
                    } else if ( xhr.readyState === 4 ) {
                        // (IE6 & IE7) if it's in cache and has been
                        // retrieved directly we need to fire the callback
                        setTimeout( callback );
                    } else {
                        handle = ++xhrId;
                        if ( xhrOnUnloadAbort ) {
                            // Create the active xhrs callbacks list if needed
                            // and attach the unload handler
                            if ( !xhrCallbacks ) {
                                xhrCallbacks = {};
                                jQuery( window ).unload( xhrOnUnloadAbort );
                            }
                            // Add to list of active xhrs callbacks
                            xhrCallbacks[ handle ] = callback;
                        }
                        xhr.onreadystatechange = callback;
                    }
                },

                abort: function() {
                    if ( callback ) {
                        callback( undefined, true );
                    }
                }
            };
        }
    });
}
//ajax模块结束