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