Jquery源码分析---Ajax分析
7.1 jquery的ajax常用方法
对于Ajax原理不深入分析。Jquery肯定也 会提供Ajax的实现。对于ajax的请求,可以分成如下的几步:
1、通过 new XMLHttpRequest 或其它的形式(指IE)生成ajax的对象xhr。
2、通 过xhr.open(type, url, async, username, password)的形式建立一个连接。
3、通过setRequestHeader设定xhr的请求头部(request header)。
4、通过send(data)请求服务器端的数据。
5、执行在xhr上注册 的onreadystatechange回调处理返回数据。
任何的lib都是在这几步之上 进行相关扩展而达到自己的功能。这几步之中,对于url,我们要考虑是跨域请 求怎么办,ajax为了安全的考虑不支持跨域请求,那么对于这个问题,Jquery和 Ext都是采用scriptTag的方式。
Jquery主要就是解决上面这问题,之后 就在这个基础之上进行扩展,如getScript,getJson之类的函数。Ajax一个很重 要的任务就是提交form。Jquery Ajax提供了如Prototype的form中 serializeElements把form的元素串行起请求字符串。这是ajax的辅助方法。
对于ajax的应用,不管对返回的数据进行如何的处理,其最终目的还是 得落在页面的显示上,也是某一个或一些dom元素上。那么我从这个需要改变内 容的dom元素(集合)出发,通过ajax去获得数据进行一些处理最终填充到给定 的元素中。这和prototype中Ajax.Updata相似。
load
Jquery对象 的load(url, params, callback)函数就是完成这类似的工作的。
// 载入远程 HTML 文件代码并插入至 DOM 中。
// 默认使 用 GET 方式 - 传递附加参数时自动转换为 POST 方式。jQuery 1.2 中,
//可以指定选择符,来筛选载入的 HTML 文档,DOM 中将仅插入筛选出的 HTML 代码。
// 语法形如 "url #some > selector"。
load : function(url, params, callback) {
if (typeof url != 'string') return this._load(url);
var off = url.indexOf(" ");// 找到第一个空格处
if (off >= 0) { ①
var selector = url.slice(off, url.length);// 第一个空格之后的字符
url = url.slice(0, off);// 第一个空格之前的字符
}
callback = callback || function() {
};
var type = "GET";// 默认的是get类型
// 这里是判断第二参数,如果是 fn,那么就是指callback
// 如果是object,那么就是指data.load(url, [data],[callback])
if (params)
if (jQuery.isFunction (params)) {
callback = params; params = null;}
else if (typeof params == 'object') {
params = jQuery.param(params);type = "POST"; ②
}
var self = this;
jQuery.ajax( {//Ajax请求 ③
url : url, type : type,dataType : "html",data : params,
complete : function(res, status) {
// 成功就注射html到所有匹配的元素中
if (status == "success" || status == "notmodified")
// selector是否指定?没有的话就是全部的内容
// 指定的话 ,就是生成dom文档的形式,之后在中间找到满足条件的元素。
// 这中间删除 scripts 是避免IE中的 'Permission Denied' 错误
self.html(selector ? jQuery("
") ④
.append(res.responseText.replace(//g, ""))
.find(selector) : res.responseText);
self.each(callback, [res.responseText, status, res]); // 执行回调⑤
}
});
return this;
},
上面的代码①可以看出load的url参数可以指定选择符,来筛 选载入的 HTML 文档,DOM 中将仅插入筛选出的 HTML 代码。 语法形如 "url #some > selector"。在④处通过html的方式插入到元素的 内部(取代)。其支持的response应该是html的片断。
param
在 ②处通过调用了jQuery.param(params)来完成对params的key/value的对象形式 转换成查寻字符串的形式。
// 串行化form子元素组成的数组或对 象形式查询字符串
param : function(a) {
var s = [];
function add(key, value) {
s[s.length] = encodeURIComponent(key) + '='
+ encodeURIComponent(value);
};
/ 对于数组的参数,每个 元素({name:xx,value:yy})都串行化为key/value的字符串。
if (a.constructor == Array || a.jquery)
jQuery.each(a, function() {
add(this.name, this.value);
});
// 对于对象{a1:{name:xx,value:yy},a2: {name:xx,value:yy}}
// 都串行化为key/value的字符串。
else
for (var j in a)
// value是数组,key 名字要重复
if (a[j] && a [j].constructor == Array)
jQuery.each(a[j], function() {
add(j, this);
});
else
add(j, jQuery.isFunction(a[j]) ? a[j]() : a[j]);
// 返回生成字符 串
return s.join("&").replace(/%20/g, "+");
}
Get&post
Load有的时 候也不能很好地完成功能,如果不是html的response。那么不能采用了。Jquery 还提供了几个静态方式:jquery.get()、jquery. getScript ()、jquery. getJSON ()、jquery. post ()。jquery. getScript ()、jquery. getJSON () 不过是在jquery.get()基础之上提供了某方面的简单的处理。Get和Post在这里 没有什么不一样,除了请求的type不一样之外。
//通过get的type 方式进行ajax的请求
get : function(url, data, callback, type) {
// 前移 arguments 如data 参数省略
if (jQuery.isFunction(data)) {
callback = data; data = null; }
return jQuery.ajax( { type : "GET", url : url,
data : data, success : callback, dataType : type
});
},
//以post方式进行ajax请求
post : function(url, data, callback, type) {
if (jQuery.isFunction(data)) {
callback = data; data = {}; }
return jQuery.ajax( {type : "POST",url : url,
data : data, success : callback,dataType : type });
},
Get和post都是在ajax的请求方面没有什 么区别。除了在服务器的接收处理的方面有所不同之外。如get在.net中是 request.querstring,而post则是采用request.form来获得。
// 取得返回的script
getScript : function(url, callback) {
return jQuery.get(url, null, callback, "script");
},
//取得json
getJSON : function(url, data, callback) {
return jQuery.get(url, data, callback, "json");
},
getScript、getJSON都是在调 用 jQuery.get,传入不同的datatype的形式来区别不同的类型。
7.2 jquery.ajax
这所有的最终都是通过jQuery.ajax()来完成的。
ajax : function(s) {
//两次继承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);
// 构建jsonp请求字符集 串。jsonp是跨域请求,要加上callback=?后面将会加函数名
if (s.dataType == "jsonp") { ②
if (type == "GET") {//使get的url包含 callback=?后面将 会进行加函数名
if (!s.url.match(jsre))
s.url += (s.url.match(/?/) ? "&" : "?")
+ (s.jsonp || "callback") + "=?";
} // 构建新的s.data,使其包含 callback=function name
else if (!s.data || !s.data.match(jsre))
s.data = (s.data ? s.data + "&" : "") + (s.jsonp||"callback")+ "=?";
s.dataType = "json";
}
//判断是否为jsonp,如果是 ,进行处理。
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");
// 我们需要保证jsonp 类 型响应能正确地执行
//jsonp的类型必须为script。这样才能执行服 务器返回的
//代码。这里就是调用这个回调函数。
s.dataType = "script";
//window下注册一个jsonp回调函数 有,让ajax请求返回的代码调用执行它,
//在服务器端我们生成的代 码 如callbackname(data);形式传入data.
window[jsonp] = function(tmp) {
data = tmp;success();complete();
// 垃圾回收,释放联变量,删除jsonp的对象,除去head中加的script元素
window[jsonp] = undefined;
try { delete window [jsonp];
} catch (e) { }
if (head) head.removeChild(script);
};
}
if (s.dataType == "script" && s.cache == null) s.cache = false;
// 加上时间戳,可见加cache:false就会加上时间戳
if (s.cache === false && type == "GET") {
var ts = now();
var ret = s.url.replace(/(?|&) _=.*?(&|$)/, "$1_=" + ts + "$2");
// 没有代替,就追加在url的尾部
s.url = ret+ ((ret == s.url) ? (s.url.match(/?/) ? "&" : "?") + "_="
+ ts : "");
}
// data有效,追加到get类型的url上去
if (s.data && type == "GET") {
s.url += (s.url.match(/?/) ? "&" : "?") + s.data;
// 防止IE会重复发送get和post data
s.data = null;
}
// 监听一个新的请求
if (s.global && ! jQuery.active++) jQuery.event.trigger("ajaxStart");④
// 监听一个绝对的url,和保存domain
var parts = /^(w+:)?//([^/? #]+)/.exec(s.url);
// 如果我们正在请求一个远程文档和正在load json 或script通过get类型
//location是window的属性,通过和地址栏中的地 址比较判断是不是跨域。
if (s.dataType == "script" && type == "GET"&& parts && (parts [1] &&
parts[1] != location.protocol || parts[2] != location.host)) {⑤
// 在head中加上
var head = document.getElementsByTagName("head")[0];
var script = document.createElement("script");
script.src = s.url;
if (s.scriptCharset) script.charset = s.scriptCharset;
//如果datatype不是jsonp,但是url却是跨域 的。采用scriptr的
//onload或onreadystatechange事件来触发回 调函数。
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);
// 已经使用 了script 元素注射来处理所有的事情
return undefined;
}
var requestDone = false;
// 创建request,IE7不能通过 XMLHttpRequest来完成,只能通过ActiveXObject
var xhr = window.ActiveXObject ⑥
? new ActiveXObject("Microsoft.XMLHTTP"): new XMLHttpRequest ();
// 创建一个请求的连接,在opera中如果用户名为null会弹出login窗 口中。
if (s.username)xhr.open(type, s.url, s.async, s.username, s.password);
else xhr.open(type, s.url, s.async);
// try/catch是为防止FF3在跨域请求时报错
try {// 设定Content-Type ⑦
if (s.data)
xhr.setRequestHeader("Content-Type", s.contentType);
// 设定If-Modified-Since
if (s.ifModified)
xhr.setRequestHeader("If-Modified- Since",
jQuery.lastModified[s.url]|| "Thu, 01 Jan 1970 00:00:00 GMT");
// 这里是为了让服务器能判断这个 请求是XMLHttpRequest
xhr.setRequestHeader("X-Requested- With", "XMLHttpRequest");
// 设定 Accepts header 。指能接收的content-type,在服务器端设定
xhr.setRequestHeader("Accept", s.dataType && s.accepts[s.dataType]
? s.accepts[s.dataType] + ", */*": s.accepts._default);
} catch (e) {}
//拦 截方法,我们可以在send之前进行拦截。返回false就不send
if (s.beforeSend && s.beforeSend(xhr, s) === false) { ⑧
// 清除active 请求计数
s.global && jQuery.active--;
xhr.abort();
return false;
}
// 触发全局的ajaxSend事件
if (s.global) jQuery.event.trigger("ajaxSend", [xhr, s]);
// 等待 response返回,主要是为后面setInterval用。
var onreadystatechange = function(isTimeout) { ⑨
// 接收成功或请求超时
if (!requestDone && xhr&& (xhr.readyState == 4 ||isTimeout == "timeout")) { requestDone = true;
//清除定时器
if (ival) {clearInterval(ival); ival = null; }
// 分析status:tiemout-->error-->notmodified-- >success
status = isTimeout == "timeout" ? "timeout" : !jQuery
ttpSuccess(xhr) ? "error" : s.ifModified&& jQuery.
httpNotModified(xhr, s.url) ? "notmodified": "success";
//如果success且返回了数据,那么分析这些 数据
if (status == "success") {
try { data = jQuery.httpData(xhr, s.dataType, s);
} catch (e) { status = "parsererror"; }
}
// 分析数据 成功之后,进行last-modified和success的处理。
if (status == "success") {
var modRes;
try {modRes = xhr.getResponseHeader("Last-Modified");
} catch (e) { //FF中如果head取不到,会抛出异常}
//保存last-mordified的标识。
if (s.ifModified && modRes)jQuery.lastModified[s.url] = modRes;
// JSONP 有自己的callback
if (!jsonp) success();
} else // 失败时的处理
jQuery.handleError(s, xhr, status);
// 无论如何都进行cpmplate.timeout和接收成功
complete();
if (s.async) xhr = null; // 防内存泄漏
}
};
if (s.async) {
// 这里是采用poll的方式,不是push的方式
//这里为 什么不采用onreadystatechange?
var ival = setInterval (onreadystatechange, 13);
//如果过了timeout还没有请求到,会中断请 求的。
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);} ⑩
// firefox 1.5 doesn't fire statechange for sync requests
if (!s.async) onreadystatechange();
function success() {
// 调用构建请 求对象时指定的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]);
// 全局的ajax计数器
if (s.global && !--jQuery.active)jQuery.event.trigger ("ajaxStop");
}
// return XMLHttpRequest便进行 about()或其它操作.
return xhr;
},
Jquery.ajax是 大包大揽的非常复杂的一个方法。它并没有像其它的lib一样,把每个小部分都 分开来。它是整个都整在一个函数中。看起来很多,实际上上也没有脱离前面所 说的ajax的请求的五步。它的很大一部分代码在处理跨域请求的处理上。下面就 分别就ajax的代码进行分析。
ajaxSettings
在①处通过继承的方 式把传入参数s和默认的jQuery.ajaxSettings都clone到s变量中。S的同名属性 会覆盖jQuery.ajaxSettings的同名属性。这里两次继承s,以便在测试中能检测 。
//默认的ajax的请求参数
ajaxSettings : {
url : location.href,//默认是地址栏中url
global : true,//默认支持全局的ajax事件
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的方式来完成。先在页面的 中添加一个