/* -----------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模块结束