由于jQuery ajax对Callbacks、Deferred、serialize、event等模块的依赖,建议对这些模块没有认识的朋友看一下 jQuery Callbacks 、 jQuery Deferred 、 jQuery serialize 、 jQuery event(上) 、 jQuery event(下) 。
这篇文章主要分析的是拥有380+行的jQuery.ajax函数,该函数是jQuery ajax的核心函数,jQuery的其他ajax方法几乎都是基于该方法的。
上一篇文章我们了解了Baidu ajax(当然是旧版的,还是被简化的……),那么我们想给这个简单的ajax方法添加什么功能呢?
可链式操作
既然是jQuery必然先要解决的是链式操作的问题。
jQuery中的Deferred可以实现异步链式模型实现,Promise对象可以轻易的绑定成功、失败、进行中三种状态的回调函数,然后通过在状态码在来回调不同的函数就行了。
但仅仅返回一个promise没什么用
promise只能处理异步,我们需要返回一个更有用的东西。
jqXHR就是这样的东西,实际上他是一个山寨版的XHR对象。
这样我们就可以扩展一下XHR对象的功能, 提供一定的容错处理,暴露给外部提供一些方法。
//
赝品xhr,或者说山寨xhr……╮(╯▽╰)╭
//
为了能够实现链式操作
//
顺便扩展一下xhr对象功能
//
还有提供一定的容错处理
jqXHR =
{
//
准备状态
readyState: 0
,
//
如果需要,创建一个响应头参数的表
getResponseHeader:
function
( key ) {
var
match;
//
如果状态为2,状态2表示ajax完成
if
( state === 2
) {
//
如果没有相应头
if
( !
responseHeaders ) {
//
相应头设空
responseHeaders =
{};
while
( (match =
rheaders.exec( responseHeadersString )) ) {
//
组装相应头
responseHeaders[ match[1].toLowerCase() ] = match[ 2
];
}
}
//
响应头对应的key的值
match =
responseHeaders[ key.toLowerCase() ];
}
//
返回
return
match ==
null
?
null
: match;
},
//
返回响应头字符串
getAllResponseHeaders:
function
() {
//
看看是否接收到了,接收到直接返回,否则为null
return
state === 2 ? responseHeadersString :
null
;
},
//
设置请求头
setRequestHeader:
function
( name, value ) {
var
lname =
name.toLowerCase();
//
如果state不为0
if
( !
state ) {
//
如果requestHeadersNames[ lname ]不为空,
//
则requestHeadersNames[ lname ]不变,name设置为该值
//
否则,requestHeadersNames[ lname ]不空,
//
则requestHeadersNames[ lname ]设置为name,
//
该映射关系用于避免用户大小写书写错误之类的问题,容错处理
name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] ||
name;
//
现在的name是对的,或者是第一次设置这个name,不需要容错
//
设置请求头对应值
requestHeaders[ name ] =
value;
}
return
this
;
},
//
重写相应头content-type
overrideMimeType:
function
( type ) {
if
( !
state ) {
s.mimeType
=
type;
}
return
this
;
},
//
对应状态的回调函数集
statusCode:
function
( map ) {
var
code;
//
如果map存在,准备组装
if
( map ) {
//
如果状态小于2,表示旧的回调可能还没有用到
if
( state < 2
) {
//
遍历map里面的所有code
for
( code
in
map ) {
//
用类似链表的方式添加,以保证旧的回调依然存在
statusCode[ code ] =
[ statusCode[ code ], map[ code ] ];
}
//
状态大于2,证明已经完成了
}
else
{
//
无论Deferred成功还是失败都执行当前状态回调
jqXHR.always( map[ jqXHR.status ] );
}
}
return
this
;
},
//
中断请求
abort:
function
( statusText ) {
var
finalText = statusText ||
strAbort;
//
可以先理解成XHR对象,当然这也不是真正的XHR对象
if
( transport ) {
transport.abort( finalText );
}
//
调用done,表示干完了
done( 0
, finalText );
return
this
;
}
};
可是这还没有链式啊!
怎么把jqXHR变成一个Promise呢?
让一个对象拥有另一个对象的方法,大家会想到什么呢?
继承?
不,直接插上去就好了……这里就体现了Javascript“易插拔”的特点……自己乱起的……囧rz……
应该说动态、弱类的特点。
什么意思?就是将Promise上的方法插到jqXHR对象上就行了。
//
在jqXHR粘上promise的所有方法,此时jqXHR就很像一个promise了
//
实际上就是用jQuery.extend(jqXHR, promise)而已
//
jqXHR刚山寨了xhr对象,又开始山寨promise对象了
//
顺便把jqXHR的complete绑上completeDeferred.add
//
意思是jqXHR状态完成后调用completeDeferred这个Callbacks列队
//
话说promise里面并没有complete这个方法
//
后面我们可以看到,作者大人又要给jqXHR插上complete、success、error方法
//
Javascript就是这样简单,即插即用……囧rz
deferred.promise( jqXHR ).complete =
completeDeferred.add;
//
绑定jqXHR.success为promise里面的done方法
jqXHR.success =
jqXHR.done;
//
绑定jqXHR.error为promise里面的fail方法
jqXHR.error = jqXHR.fail;
这样子直接插上去在强类语言中是做不到的,当然也可以用组合模式来实现一个对象拥有多个对象的方法,但是却无法动态的去给对象添加各种方法。
强类语言会限制对象一辈子只能“会”那么几种“方法”,Javascript的对象可以在生命周期内随意“学习”各种“方法”。
所以说,强类语言和Javascript的对象模型是有差别的,将设计模式生搬硬套进Javascript或许是错误的。
我们再举个例子,比如如果用所谓的Javascript组合模式来重写上面的代码可能会是这样的:
// 定义局部变量deferred和completeDeferred function JQXHR(){ // 定义jqXHR的属性和方法 for (i in deferred.promise){ this [i] = this .promise[i]; } this .complete = completeDeferred.add; this .success = this .done; this .error = this .done } var jqXHR = new JQXHR();大家觉得哪个好呢?
变成上面的样子主要是因为要解决下面两个问题:
- jqXHR由于是暴露在外的,他不能包含deferred和completeDeferred,不允许用户在外部操作这两个对象的状态。
- deferred和completeDeferred也不能成为jqXHR的私有变量,因为还要更具ajax事件触发。
提供全局事件,使得UI可以根据ajax状态进行改变
这里主要利用了jQuery.event.trigger和jQuery.fn.trigger模拟发事件。
//
如果需要,而且全局事件没有被触发过
if
( fireGlobals && jQuery.active++ === 0
) {
//
则通过jQuery.event.trigger模拟触发
jQuery.event.trigger("ajaxStart"
);
}
当ajax开始时模拟全局事件,ajaxStart。
//
如果需要,对特定对象触发全局事件ajaxSend
if
( fireGlobals ) {
globalEventContext.trigger(
"ajaxSend"
, [ jqXHR, s ] );
}
ajax发送消息,触发ajaxSend。
//
如果需要触发全局事件
if
( fireGlobals ) {
//
对指定元素触发事件ajaxSuccess或者ajaxError
globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError"
,
[ jqXHR, s, isSuccess
?
success : error ] );
}
//
回调完成后的Callbacks队列
completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
//
如果需要触发全局事件
if
( fireGlobals ) {
//
对指定元素触发事件ajaxComplete
globalEventContext.trigger( "ajaxComplete"
, [ jqXHR, s ] );
//
该ajax触发完毕,标记active减1,如果为0,证明所有ajax结束
if
( !( --
jQuery.active ) ) {
//
触发ajaxStop事件
jQuery.event.trigger("ajaxStop"
);
}
}
结束时候触发ajaxSuccess或ajaxError,再出发ajaxComplete,如果全部ajax结束则触发ajaxStop。
是否允许使用缓存数据
//
如果不需要content
//
看看需不需要加其他信息
if
( !
s.hasContent ) {
//
如果data存在,那么已经序列化
if
( s.data ) {
//
添加到cacheURL中
cacheURL = ( s.url += ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) +
s.data );
//
删除掉则后面就不会被发送出去了
delete
s.data;
}
//
看看是否需要避免数据从缓存中读取
if
( s.cache ===
false
) {
s.url
= rts.test( cacheURL ) ?
//
如果已经有_参数,那么设置他的值
cacheURL.replace( rts, "$1_=" + ajax_nonce++
) :
//
否则添加一个_ = xxx在URL后面
cacheURL + ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ajax_nonce++
;
}
}
通过给地址附加参数_=xxx来避免缓存。
//
看看需不需要设置If-Modified-Since和If-None-Match头信息
if
( s.ifModified ) {
if
( jQuery.lastModified[ cacheURL ] ) {
jqXHR.setRequestHeader(
"If-Modified-Since"
, jQuery.lastModified[ cacheURL ] );
}
if
( jQuery.etag[ cacheURL ] ) {
jqXHR.setRequestHeader(
"If-None-Match"
, jQuery.etag[ cacheURL ] );
}
}
以及设置If-Modified-Since和If-None-Match头信息。
//
看看是否需要缓存置If-Modified-Since和If-None-Match头
if
( s.ifModified ) {
//
取得Last-Modified
modified = jqXHR.getResponseHeader("Last-Modified"
);
//
如果Last-Modified存在
if
( modified ) {
//
在jQuery.lastModified[cacheURL]保存Last-Modified
jQuery.lastModified[ cacheURL ] =
modified;
}
//
取得etag
modified = jqXHR.getResponseHeader("etag"
);
//
如果etag存在
if
( modified ) {
//
在jQuery.etag[cacheURL]缓存etag
jQuery.etag[ cacheURL ] =
modified;
}
}
缓存 If-Modified-Since和If-None-Match头信息。
设置超时
//
如果是异步,并且设置了超时
if
( s.async && s.timeout > 0
) {
//
设置超时
timeoutTimer = setTimeout(
function
() {
jqXHR.abort(
"timeout"
);
}, s.timeout );
}
主要功能分析完了,完整备注代码见下。
完整备注
jQuery.ajax =
function
( url, options ) {
//
如果url是一个obj,模拟1.5版本以前的方法
if
(
typeof
url === "object"
) {
options
=
url;
url
=
undefined;
}
//
设置options
options = options ||
{};
var
transport,
//
缓存cacheURL
cacheURL,
//
响应头
responseHeadersString,
responseHeaders,
//
超时控制器
timeoutTimer,
//
跨域判定变量
parts,
//
是否需要触发全局事件
fireGlobals,
//
循环变量
i,
//
通过jQuery.ajaxSetup改造参数对象
s =
jQuery.ajaxSetup( {}, options ),
//
回调指定上下文,也就是他的this
callbackContext = s.context ||
s,
//
全局事件中的相应函数的指定上下文
//
有s.context,且是DOM节点,或者jQuery收集器
globalEventContext = s.context && ( callbackContext.nodeType || callbackContext.jquery ) ?
//
通过jQuery包装
jQuery( callbackContext ) :
//
否则为jQuery.event
jQuery.event,
//
新建一个deferred
deferred =
jQuery.Deferred(),
//
deferred完成后的Callbacks队列
completeDeferred = jQuery.Callbacks("once memory"
),
//
对应状态的回调函数集
statusCode = s.statusCode ||
{},
//
请求头
requestHeaders =
{},
requestHeadersNames
=
{},
//
包装类jqXHR的状态
state = 0
,
//
默认中断消息
strAbort = "canceled"
,
//
赝品xhr,或者说山寨xhr……╮(╯▽╰)╭
//
为了能够实现链式操作
//
顺便扩展一下xhr对象功能
//
还有提供一定的容错处理
jqXHR =
{
//
准备状态
readyState: 0
,
//
如果需要,创建一个响应头参数的表
getResponseHeader:
function
( key ) {
var
match;
//
如果状态为2,状态2表示ajax完成
if
( state === 2
) {
//
如果没有相应头
if
( !
responseHeaders ) {
//
相应头设空
responseHeaders =
{};
while
( (match =
rheaders.exec( responseHeadersString )) ) {
//
组装相应头
responseHeaders[ match[1].toLowerCase() ] = match[ 2
];
}
}
//
响应头对应的key的值
match =
responseHeaders[ key.toLowerCase() ];
}
//
返回
return
match ==
null
?
null
: match;
},
//
返回响应头字符串
getAllResponseHeaders:
function
() {
//
看看是否接收到了,接收到直接返回,否则为null
return
state === 2 ? responseHeadersString :
null
;
},
//
缓存请求头
setRequestHeader:
function
( name, value ) {
var
lname =
name.toLowerCase();
//
如果state不为0
if
( !
state ) {
//
如果requestHeadersNames[ lname ]不为空,
//
则requestHeadersNames[ lname ]不变,name设置为该值
//
否则,requestHeadersNames[ lname ]不空,
//
则requestHeadersNames[ lname ]设置为name,
//
该映射关系用于避免用户大小写书写错误之类的问题,容错处理
name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] ||
name;
//
现在的name是对的,或者是第一次设置这个name,不需要容错
//
设置请求头对应值
requestHeaders[ name ] =
value;
}
return
this
;
},
//
重写相应头content-type
overrideMimeType:
function
( type ) {
if
( !
state ) {
s.mimeType
=
type;
}
return
this
;
},
//
对应状态的回调函数集
statusCode:
function
( map ) {
var
code;
//
如果map存在,准备组装
if
( map ) {
//
如果状态小于2,表示旧的回调可能还没有用到
if
( state < 2
) {
//
遍历map里面的所有code
for
( code
in
map ) {
//
用类似链表的方式添加,以保证旧的回调依然存在
statusCode[ code ] =
[ statusCode[ code ], map[ code ] ];
}
//
状态大于2,证明已经完成了
}
else
{
//
无论Deferred成功还是失败都执行当前状态回调
jqXHR.always( map[ jqXHR.status ] );
}
}
return
this
;
},
//
中断请求
abort:
function
( statusText ) {
var
finalText = statusText ||
strAbort;
//
可以先理解成XHR对象,当然这也不是真正的XHR对象
if
( transport ) {
transport.abort( finalText );
}
//
调用done,表示干完了
done( 0
, finalText );
return
this
;
}
};
//
在jqXHR粘上promise的所有方法,此时jqXHR就很像一个promise了
//
实际上就是用jQuery.extend(jqXHR, promise)而已
//
jqXHR刚山寨了xhr对象,又开始山寨promise对象了
//
顺便把jqXHR的complete绑上completeDeferred.add
//
意思是jqXHR状态完成后调用completeDeferred这个Callbacks列队
//
话说promise里面并没有complete这个方法
//
后面我们可以看到,作者大人又要给jqXHR插上complete、success、error方法
//
Javascript就是这样简单,即插即用……囧rz
deferred.promise( jqXHR ).complete =
completeDeferred.add;
//
绑定jqXHR.success为promise里面的done方法
jqXHR.success =
jqXHR.done;
//
绑定jqXHR.error为promise里面的fail方法
jqXHR.error =
jqXHR.fail;
//
确定url参数,否则用当前地址。将地址的#号后面的所有东西去掉
//
比如http://127.0.0.1#main,去掉这个#main
//
如果开头是//,及数据传输协议没有,那么用当前页面的数据传输协议替换
//
比如//127.0.0.1,变成http://127.0.0.1
s.url = ( ( url || s.url || ajaxLocation ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//"
);
//
定义type,向后兼容
s.type = options.method || options.type || s.method ||
s.type;
//
取出数据类型列表
//
没有则为"*",
//
有则认为是用空格分隔的字符串,将其变成数组
s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().match( core_rnotwhite ) || [""
];
//
当协议、主机、端口和当前不匹配时,证明这是一个跨域请求
if
( s.crossDomain ==
null
) {
//
分隔当前url
parts =
rurl.exec( s.url.toLowerCase() );
//
判定是否是跨域
s.crossDomain = !!( parts &&
( parts[
1 ] !== ajaxLocParts[ 1 ] || parts[ 2 ] !== ajaxLocParts[ 2 ] ||
( parts[
3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) !=
( ajaxLocParts[
3 ] || ( ajaxLocParts[ 1 ] === "http:" ? 80 : 443
) ) )
);
}
//
如果data已经是一个字符串了,那么就不用转换了
if
( s.data && s.processData &&
typeof
s.data !== "string"
) {
//
序列化
s.data =
jQuery.param( s.data, s.traditional );
}
//
预过滤
inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
//
如果请求被prefilter终止,则退出
if
( state === 2
) {
return
jqXHR;
}
//
看看需不需要触发全局事件
fireGlobals =
s.global;
//
如果需要,而且全局事件没有被触发过
if
( fireGlobals && jQuery.active++ === 0
) {
//
则通过jQuery.event.trigger模拟触发
jQuery.event.trigger("ajaxStart"
);
}
//
将类型大写
s.type =
s.type.toUpperCase();
//
判断需不需要设置content
s.hasContent = !
rnoContent.test( s.type );
//
缓存URL,用来在之后设置If-Modified-Since和If-None-Match
cacheURL =
s.url;
//
如果不需要content
//
看看需不需要加其他信息
if
( !
s.hasContent ) {
//
如果data存在,那么已经序列化
if
( s.data ) {
//
添加到cacheURL中
cacheURL = ( s.url += ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) +
s.data );
//
删除掉则后面就不会被发送出去了
delete
s.data;
}
//
看看是否需要避免数据从缓存中读取
if
( s.cache ===
false
) {
s.url
= rts.test( cacheURL ) ?
//
如果已经有_参数,那么设置他的值
cacheURL.replace( rts, "$1_=" + ajax_nonce++
) :
//
否则添加一个_ = xxx在URL后面
cacheURL + ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ajax_nonce++
;
}
}
//
看看需不需要设置If-Modified-Since和If-None-Match头信息
if
( s.ifModified ) {
if
( jQuery.lastModified[ cacheURL ] ) {
jqXHR.setRequestHeader(
"If-Modified-Since"
, jQuery.lastModified[ cacheURL ] );
}
if
( jQuery.etag[ cacheURL ] ) {
jqXHR.setRequestHeader(
"If-None-Match"
, jQuery.etag[ cacheURL ] );
}
}
//
如果数据需要被发送,设置正确的头
if
( s.data && s.hasContent && s.contentType !==
false
||
options.contentType ) {
jqXHR.setRequestHeader(
"Content-Type"
, s.contentType );
}
//
根据dataType,设置一个Accept头
jqXHR.setRequestHeader(
"Accept"
,
s.dataTypes[
0 ] && s.accepts[ s.dataTypes[0] ] ?
s.accepts[ s.dataTypes[
0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : ""
) :
s.accepts[
"*"
]
};
//
遍历s.headers,将其内参数设置入请求头
for
( i
in
s.headers ) {
jqXHR.setRequestHeader( i, s.headers[ i ] );
}
//
通过beforeSend检查是否需要发送
if
( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) ===
false
|| state === 2
) ) {
//
终止
return
jqXHR.abort();
}
//
此时abort函数不是取消ajax,而是中断了ajax
strAbort = "abort"
;
//
在jqXHR上绑定成功、错误、完成回调函数
for
( i
in
{ success: 1, error: 1, complete: 1
} ) {
jqXHR[ i ]( s[ i ] );
}
//
得到transport
transport =
inspectPrefiltersOrTransports( transports, s, options, jqXHR );
//
如果没有,自动终止
if
( !
transport ) {
done(
-1, "No Transport"
);
}
else
{
//
否则,设置reayState为1
jqXHR.readyState = 1
;
//
如果需要,对特定对象触发全局事件ajaxSend
if
( fireGlobals ) {
globalEventContext.trigger(
"ajaxSend"
, [ jqXHR, s ] );
}
//
如果是异步,并且设置了超时
if
( s.async && s.timeout > 0
) {
//
设置超时
timeoutTimer = setTimeout(
function
() {
jqXHR.abort(
"timeout"
);
}, s.timeout );
}
try
{
//
设置state为1
state = 1
;
//
开始发送
transport.send( requestHeaders, done );
}
catch
( e ) {
//
截获错误,如果ajax未完成
if
( state < 2
) {
done(
-1
, e );
//
完成了就直接抛出错误
}
else
{
throw
e;
}
}
}
//
完成时的回调函数
function
done( status, nativeStatusText, responses, headers ) {
var
isSuccess, success, error, response, modified,
statusText
=
nativeStatusText;
//
如果已经调用过该函数,直接退出
if
( state === 2
) {
return
;
}
//
设置现在状态已完成
state = 2
;
//
清除超时设置
if
( timeoutTimer ) {
clearTimeout( timeoutTimer );
}
//
不管jqXHR对象要被用到何时,
//
释放transport的引用使得他可以先被垃圾回收
transport =
undefined;
//
缓存响应头
responseHeadersString = headers || ""
;
//
设置readyState
jqXHR.readyState = status > 0 ? 4 : 0
;
//
得到响应数据
if
( responses ) {
//
通过ajaxHandleResponses处理数据
response =
ajaxHandleResponses( s, jqXHR, responses );
}
//
If successful, handle type chaining
//
如果成功
if
( status >= 200 && status < 300 || status === 304
) {
//
看看是否需要缓存If-Modified-Since和If-None-Match头
if
( s.ifModified ) {
//
取得Last-Modified
modified = jqXHR.getResponseHeader("Last-Modified"
);
//
如果Last-Modified存在
if
( modified ) {
//
在jQuery.lastModified[cacheURL]保存Last-Modified
jQuery.lastModified[ cacheURL ] =
modified;
}
//
取得etag
modified = jqXHR.getResponseHeader("etag"
);
//
如果etag存在
if
( modified ) {
//
在jQuery.etag[cacheURL]缓存etag
jQuery.etag[ cacheURL ] =
modified;
}
}
//
如果没有修改
if
( status === 304
) {
//
设置成功
isSuccess =
true
;
//
设置状态为notmodified
statusText = "notmodified"
;
//
否则得到必要的数据
}
else
{
isSuccess
=
ajaxConvert( s, response );
statusText
=
isSuccess.state;
success
=
isSuccess.data;
error
=
isSuccess.error;
isSuccess
= !
error;
}
//
如果失败
}
else
{
//
从statusText获取error状态
//
在设置statusText成"error"
//
并改变status为0
error =
statusText;
if
( status || !
statusText ) {
statusText
= "error"
;
if
( status < 0
) {
status
= 0
;
}
}
}
//
开始设置山寨xhr对象jqXHR的status和statusText
jqXHR.status =
status;
jqXHR.statusText
= ( nativeStatusText || statusText ) + ""
;
//
根据成功还是失败,对deferred进行回调
if
( isSuccess ) {
deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
}
else
{
deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
}
//
根据目前statusCode回调
jqXHR.statusCode( statusCode );
statusCode
=
undefined;
//
如果需要触发全局事件
if
( fireGlobals ) {
//
对指定元素触发事件ajaxSuccess或者ajaxError
globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError"
,
[ jqXHR, s, isSuccess
?
success : error ] );
}
//
回调完成后的Callbacks队列
completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
//
如果需要触发全局事件
if
( fireGlobals ) {
//
对指定元素触发事件ajaxComplete
globalEventContext.trigger( "ajaxComplete"
, [ jqXHR, s ] );
//
该ajax触发完毕,标记active减1,如果为0,证明所有ajax结束
if
( !( --
jQuery.active ) ) {
//
触发ajaxStop事件
jQuery.event.trigger("ajaxStop"
);
}
}
}
//
返回山寨xhr
return
jqXHR;
};

