这所有的最终都是通过jQuery.ajax()来完成的。
ajax : function(s) {
s = jQuery.extend( true , s, jQuery.extend(true , {},
jQuery.ajaxSettings, s)); ①
var jsonp, jsre = /=\?(&|$)/g, status, data,
type = s.type .toUpperCase();
if (s.data && s.processData && typeof s.data != "string" )
s.data = jQuery.param(s.data);
if (s.dataType == "jsonp" ) { ②
if (type == "GET" ) {
if (!s.url.match(jsre))
s.url += (s.url.match(/\?/) ? "&" : "?" )
+ (s.jsonp || "callback" ) + "=?" ;
}
else if (!s.data || !s.data.match(jsre))
s.data = (s.data ? s.data + "&" : "" ) + (s.jsonp||"callback" )+ "=?" ;
s.dataType = "json" ;
}
if (s.dataType == "json"
&& (s.data && s.data.match(jsre) || s.url.match(jsre))) {③
jsonp = "jsonp" + jsc++;
/ /为请求字符集串的callback=加上生成回调函数名
if (s.data)s.data = (s.data + "" ).replace(jsre, "=" + jsonp + "$1" );
s.url = s.url.replace(jsre, "=" + jsonp + "$1" );
s.dataType = "script" ;
window[jsonp] = function(tmp) {
data = tmp;success();complete();
window[jsonp] = undefined;
try { delete window[jsonp];
} catch (e) { }
if (head) head.removeChild(script);
};
}
if (s.dataType == "script" && s.cache == null ) s.cache = false ;
if (s.cache === false && type == "GET" ) {
var ts = now();
var ret = s.url.replace(/(\?|&)_=.*?(&|$)/, "$1_=" + ts + "$2" );
s.url = ret+ ((ret == s.url) ? (s.url.match(/\?/) ? "&" : "?" ) + "_="
+ ts : "" );
}
if (s.data && type == "GET" ) {
s.url += (s.url.match(/\?/) ? "&" : "?" ) + s.data;
s.data = null ;
}
if (s.global && !jQuery.active++) jQuery.event.trigger("ajaxStart" );④
var parts = /^(\w+:)?\/\/([^\/?#]+)/.exec(s.url);
if (s.dataType == "script" && type == "GET" && parts && (parts[1 ] &&
parts[ 1 ] != location.protocol || parts[2 ] != location.host)) {⑤
var head = document.getElementsByTagName( "head" )[0 ];
var script = document.createElement( "script" );
script.src = s.url;
if (s.scriptCharset) script.charset = s.scriptCharset;
if (!jsonp) {
var done = false ;
script.onload = script.onreadystatechange = function() {
if (!done&& (!this .readyState || this .readyState == "loaded"
|| this .readyState == "complete" )) {
done = true ; success();
complete();head.removeChild(script);
}
};
}
head.appendChild(script);
return undefined;
}
var requestDone = false ;
var xhr = window.ActiveXObject ⑥
? new ActiveXObject("Microsoft.XMLHTTP" ): new XMLHttpRequest();
if (s.username)xhr.open(type, s.url, s.async, s.username, s.password);
else xhr.open(type, s.url, s.async);
try {
if (s.data)
xhr.setRequestHeader( "Content-Type" , s.contentType);
if (s.ifModified)
xhr.setRequestHeader( "If-Modified-Since" ,
jQuery.lastModified[s.url]|| "Thu, 01 Jan 1970 00:00:00 GMT" );
xhr.setRequestHeader( "X-Requested-With" , "XMLHttpRequest" );
xhr.setRequestHeader( "Accept" , s.dataType && s.accepts[s.dataType]
? s.accepts[s.dataType] + ", */*" : s.accepts._default);
} catch (e) {}
if (s.beforeSend && s.beforeSend(xhr, s) === false ) { ⑧
s.global && jQuery.active--;
xhr.abort();
return false ;
}
if (s.global) jQuery.event.trigger("ajaxSend" , [xhr, s]);
var onreadystatechange = function(isTimeout) { ⑨
if (!requestDone && xhr&& (xhr.readyState == 4 ||isTimeout == "timeout" )) { requestDone = true ;
if (ival) {clearInterval(ival); ival = null ; }
status = isTimeout == "timeout" ? "timeout" : !jQuery
ttpSuccess(xhr) ? "error" : s.ifModified&& jQuery.
httpNotModified(xhr, s.url) ? "notmodified" : "success" ;
if (status == "success" ) {
try { data = jQuery.httpData(xhr, s.dataType, s);
} catch (e) { status = "parsererror" ; }
}
if (status == "success" ) {
var modRes;
try {modRes = xhr.getResponseHeader("Last-Modified" );
} catch (e) {
if (s.ifModified && modRes)jQuery.lastModified[s.url] = modRes;
if (!jsonp) success();
} else
jQuery.handleError(s, xhr, status);
complete();
if (s.async) xhr = null ;
}
};
if (s.async) {
var ival = setInterval(onreadystatechange, 13 );
if (s.timeout > 0 )
setTimeout(function() {
if (xhr) { xhr.abort();
if (!requestDone) onreadystatechange("timeout" ); }
}, s.timeout);
}
try {xhr.send(s.data); catch (e){jQuery.handleError(s,xhr,null ,e);} ⑩
if (!s.async) onreadystatechange();
function success() {
if (s.success) s.success(data, status);
if (s.global) jQuery.event.trigger("ajaxSuccess" , [xhr, s]);
}
function complete() {
if (s.complete) s.complete(xhr, status);
if (s.global) jQuery.event.trigger("ajaxComplete" , [xhr, s]);
if (s.global && !--jQuery.active)jQuery.event.trigger("ajaxStop" );
}
return xhr;
},
Jquery.ajax是大包大揽的非常复杂的一个方法。它并没有像其它的lib一样,把每个小部分都分开来。它是整个都整在一个函数中。看起来很多,实际上上也没有脱离前面所说的ajax的请求的五步。它的很大一部分代码在处理跨域请求的处理上。下面就分别就ajax的代码进行分析。
ajaxSettings
在①处通过继承的方式把传入参数s和默认的jQuery.ajaxSettings都clone到s变量中。S的同名属性会覆盖jQuery.ajaxSettings的同名属性。这里两次继承s,以便在测试中能检测。
ajaxSettings : {
url : location.href,
global : true ,
type : "GET" ,
timeout : 0 ,
contentType : "application/x-www-form-urlencoded" ,
processData : true ,
async : true ,
data : null ,
username : null ,
password : null ,
accepts : {
xml : "application/xml, text/xml" ,
html : "text/html" ,
script : "text/javascript, application/javascript" ,
json : "application/json, text/javascript" ,
text : "text/plain" ,
_default : "*/*"
}
这是默认的ajax的设定,我们要在参数s设定同名的属性来覆盖这些属性。但是我们不能覆盖accepts。这个会在后面的代码用到。我们可以通过设定s.dataType等于accepts中的某一个属性key指定请求的data类型,如xml,html,script,json,text。dataType还支持默认的_default和跨域的jsonp。不过其最终会解析成script。
scriptTag
②~⑥是处理跨域请求的部分。对于dataType为jsonp的类型,给其请求的字符串(可能是s.data)加上callback=callbackfn的key/value串,然后在window下注册一个callbackfn的函数。这个函数的形式如callbackfn(data){ data = tmp;success();complete();}。它代理了通过ajax(s)的传入s参数中success();complete()的功能。它就是调用这个函数,实际上是调用success();complete()的函数。
那么怎么调用呢?ajax不支持跨域。在⑤处,我们可以看到这里是采用scriptTag的方式来完成。先在页面的<head>中添加一个<script src=url />的标签。因为在<head>中。浏览器会自动载入并运行请求返回的script。如果是jsonp的形式,服务器端还要动态生成的content-type为script的代码:callbackfn(data);只有这样才会调用在window中注册的函数callbackfn。同时传入所需要的参数。
如dataType == "script" 形式的跨域,那只能是通过script.onload 或 script.onreadystatechange事件来触发回调。这里我们可以通过服务器返回的script代码:var data=xxx。来传递参数给s.success();s.complete()。Jquery这里采用是全局变量data来进行操作的。
Ajax Event
④是采用了jQuery.event.trigger("ajaxStart" );来触发全局的ajaxStart事件。这也是说只要注册了这个事件的元素,在任何的ajax的请求时ajaxStart都会执行元素注册的事件处理函数。这和Ext的事件有点相似。但是它不是全局的。
jQuery.each( "ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend" .split("," ), function(i, o) {
jQuery.fn[o] = function(f) {
return this .bind(o, f);
};
上面的代码是为jquery对象注册了ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend这几种ajax的事件方法,在jquery.ajax中不同的时刻都会触发这些事件。当然我们也可以采用s.global=false 来设定不触发这些事件。
因为这是全局的,个人认为其设计的目的就是为了在这些时候能以某种形式来告诉用户ajax的进行的状态。如在ajaxstart的时候,我们可能通过一个topest的div层(加上遮罩的效果)的元素注册一个ajaxstart事件的处理方法。该方法就是显示这个层和显示“你的数据正在提交。。。”这个的提示。这是这 6 种事件的最佳用法了。
如果进行私有处理,那么要在事件的处理函数中进行判断。因为每个事件处理函数的第二参数是jquery.ajax(s)的s参数。我们可以在这个参数中做私有的标识,如eventType:xxx。每类不同的请求有不同的eventType值。在事件处理函数再根据这个eventType==xxx进行判断,从而进行私有的处理。如果有大量的这样的私有处理也是会影响ajax的效率的。
setRequestHeader
⑥处是创建一个xhr对象并通过open来创建一个连接(socket)。
⑦处是设定请求的头部(setRequestHeader)。如果data的存在的话,那就得设定Content-Type,便于服务器按一定的规则来解码。可以看出post的方式通过data传递数据要安全一点。
那么服务器如果区别这个请求是ajax呢?因为同步和异步ajax的请求的头文件是一样的。我们如果通过X-Requested-With "=" XMLHttpRequest”来标识这个请求是ajax的请求。如果服务器硬是要区分的话,就可以通过获取该头部来判断。
在头部的定义中,还可能通过Accept来指定接受的数据的类型,如application/xml, text/xml ", " text/html", " text/javascript, 等等。
头部还有一个If-Modified-Since的属性用来提高效率的。它和”Last-Modified配合起来使用。在浏览器第一次请求某一个URL时,服务器端的返回状态会是 200 ,内容是你请求的资源,同时有一个Last-Modified的属性标记此文件在服务期端最后被修改的时间,格式类似这样:Last-Modified: Fri, 12 May 2006 18 :53 :33 GMT
客户端第二次请求此URL时,根据 HTTP 协议的规定,浏览器会向服务器传送 If-Modified-Since 报头,询问该时间之后文件是否有被修改过: If-Modified-Since: Fri, 12 May 2006 18 :53 :33 GMT 如果服务器端的资源没有变化,则自动返回 HTTP 304 (Not Changed.)状态码,内容为空,这样就节省了传输数据量。
当服务器端代码发生改变或者重启服务器时,则重新发出资源,返回和第一次请求时类似。从而保证不向客户端重复发出资源,也保证当服务器有变化时,客户端能够得到最新的资源。
拦截处理
⑧处是一个send之前的拦截处理,可以通过s. beforeSend(xhr, s)函数的形式传入拦截函数。保证在发送之前确保满足某些条件。在取得返回数据的时候,也可以通过s.dataFilter(data, type);形式来拦截处理data。不过这里主要的作用对data进一步的筛选。
onreadystatechange
⑨处是onreadystatechange的回调处理。这里采用是poll的形式进行处理。它把返回的状态分成status:tiemout-->error-->notmodified-->success—>parsererror这几种。如果status == "success" 那么分析这些数据之后再进行last-modified相关的处理。为了不取回没有修改过数据。
分析数据的代码如下:
httpData : function(xhr, type, s) {
var ct = xhr.getResponseHeader("content-type" ),
xml = type == "xml" || !type && ct && ct.indexOf("xml" ) >= 0 ,
data = xml? xhr.responseXML : xhr.responseText;
if (xml && data.documentElement.tagName == "parsererror" )
throw "parsererror" ;
if (s && s.dataFilter)
data = s.dataFilter(data, type);
if (type == "script" ) jQuery.globalEval(data);
if (type == "json" ) data = eval("(" + data + ")" );
return data;
},
如果返回的content-type是xml,html,text等都返回。对script执行jQuery.globalEval来执行它。对于Json类型,通过eval来生成返回的json对象。
globalEval : function(data) {
data = jQuery.trim(data);
if (data) {
var head = document.getElementsByTagName( "head" )[0 ]
|| document.documentElement,
script = document.createElement("script" );
script.type = "text/javascript" ;
if (jQuery.browser.msie) script.text = data;
else script.appendChild(document.createTextNode(data));
head.insertBefore(script, head.firstChild);
head.removeChild(script);
} },
posted @
2008-12-03 10:19
瑞尼书苑
阅读(
769 )
评论()
编辑
收藏
举报