jQuery源码分析系列:AJAX
jQuery的实现表示很复杂,但是也能学到很多的东西。
jQuery确实是一个挺好的轻量级的JS框架,能帮助我们快速的开发JS应用,并在一定程度上改变了我们写JavaScript代码的习惯。
废话少说,直接进入正题,我们先来看一些简单的方法,这些方法都是对jQuery.ajax()进行封装以方便我们使用的方法,当然,如果要处理复杂的逻辑,还是需要用到jQuery.ajax()的(这个后面会说到).
1. load( url, [data], [callback] ) :载入远程 HTML 文件代码并插入至 DOM 中。
url (String) : 请求的HTML页的URL地址。
data (Map) : (可选参数) 发送至服务器的 key/value 数据。
callback (Callback) : (可选参数) 请求完成时(不需要是success的)的回调函数。
这个方法默认使用 GET 方式来传递的,如果[data]参数有传递数据进去,就会自动转换为POST方式的。jQuery 1.2 中,可以指定选择符,来筛选载入的 HTML 文档,DOM 中将仅插入筛选出的 HTML 代码。语法形如 "url #some > selector"。
这个方法可以很方便的动态加载一些HTML文件,例如表单。
示例代码:
$(".ajax.load").load("http://www.cnblogs.com/QLeelulu/archive/2008/03/30/1130270.html .post", function (responseText, textStatus, XMLHttpRequest){ this;//在这里this指向的是当前的DOM对象,即$(".ajax.load")[0] //alert(responseText);//请求返回的内容 //alert(textStatus);//请求状态:success,error //alert(XMLHttpRequest);//XMLHttpRequest对象 });
注:不知道为什么URL写绝对路径在FF下会出错,知道的麻烦告诉下。下面的get()和post()示例使用的是绝对路径,所以在FF下你将会出错并不会看到返回结果。还有get()和post()示例都是跨域调用的,发现传上来后没办法获取结果,所以把运行按钮去掉了。
2. jQuery.get( url, [data], [callback] ):使用GET方式来进行异步请求
参数:
url (String) : 发送请求的URL地址.
data (Map) : (可选) 要发送给服务器的数据,以 Key/value 的键值对形式表示,会做为QueryString附加到请求URL中。
callback (Function) : (可选) 载入成功时回调函数(只有当Response的返回状态是success才是调用该方法)。
这是一个简单的 GET 请求功能以取代复杂 $.ajax 。请求成功时可调用回调函数。如果需要在出错时执行函数,请使用 $.ajax。示例代码:
$.get("./Ajax.aspx", {Action:"get",Name:"lulu"}, function (data, textStatus){ //返回的 data 可以是 xmlDoc, jsonObj, html, text, 等等. this; // 在这里this指向的是Ajax请求的选项配置信息,请参考下图 alert(data); //alert(textStatus);//请求状态:success,error等等。
当然这里捕捉不到error,因为error的时候根本不会运行该回调函数 //alert(this); });
点击发送请求:
jQuery.get()回调函数里面的 this ,指向的是Ajax请求的选项配置信息:
3. jQuery.post( url, [data], [callback], [type] ) :使用POST方式来进行异步请求
参数:
url (String) : 发送请求的URL地址.
data (Map) : (可选) 要发送给服务器的数据,以 Key/value 的键值对形式表示。
callback (Function) : (可选) 载入成功时回调函数(只有当Response的返回状态是success才是调用该方法)。
type (String) : (可选)官方的说明是:Type of data to be sent。其实应该为客户端请求的类型(JSON,XML,等等)
这是一个简单的 POST 请求功能以取代复杂 $.ajax 。请求成功时可调用回调函数。如果需要在出错时执行函数,请使用 $.ajax。示例代码:
Ajax.aspx:
Response.ContentType = "application/json"; Response.Write("{result: '" + Request["Name"] + ",你好!(这消息来自服务器)'}");
jQuery 代码:
$.post("Ajax.aspx", { Action: "post", Name: "lulu" }, function (data, textStatus){ // data 可以是 xmlDoc, jsonObj, html, text, 等等. //this; // 这个Ajax请求的选项配置信息,请参考jQuery.get()说到的this alert(data.result); }, "json");
点击提交:
这里设置了请求的格式为"json":
如果你设置了请求的格式为"json",此时你没有设置Response回来的ContentType 为:Response.ContentType = "application/json"; 那么你将无法捕捉到返回的数据。
注意一下,alert(data.result); 由于设置了Accept报头为“json”,这里返回的data就是一个对象,并不需要用eval()来转换为对象。
4. jQuery.getScript( url, [callback] ) : 通过 GET 方式请求载入并执行一个 JavaScript 文件。
参数
url (String) : 待载入 JS 文件地址。
callback (Function) : (可选) 成功载入后回调函数。
jQuery 1.2 版本之前,getScript 只能调用同域 JS 文件。 1.2中,您可以跨域调用 JavaScript 文件。注意:Safari 2 或更早的版本不能在全局作用域中同步执行脚本。如果通过 getScript 加入脚本,请加入延时函数。
这个方法可以用在例如当只有编辑器focus()的时候才去加载编辑器需要的JS文件.下面看一些示例代码:
加载并执行 test.js。
jQuery 代码:
$.getScript("test.js");
加载并执行 AjaxEvent.js ,成功后显示信息。
jQuery 代码:
$.getScript("AjaxEvent.js", function(){ alert("AjaxEvent.js 加载完成并执行完成.你再点击上面的Get或Post按钮看看有什么不同?"); });
加载完后请重新点击一下上面的 Load 请求看看有什么不同。
jQuery Ajax 事件
Ajax请求会产生若干不同的事件,我们可以订阅这些事件并在其中处理我们的逻辑。在jQuery这里有两种Ajax事件:局部事件 和 全局事件。
局部事件就是在每次的Ajax请求时在方法内定义的,例如:
$.ajax({ beforeSend: function(){ // Handle the beforeSend event }, complete: function(){ // Handle the complete event } // ... });
全局事件是每次的Ajax请求都会触发的,它会向DOM中的所有元素广播,在上面 getScript() 示例中加载的脚本就是全局Ajax事件。全局事件可以如下定义:
$("#loading").bind("ajaxSend", function(){ $(this).show(); }).bind("ajaxComplete", function(){ $(this).hide(); });
或者:
$("#loading").ajaxStart(function(){ $(this).show(); });
我们可以在特定的请求将全局事件禁用,只要设置下 global 选项就可以了:
$.ajax({ url: "test.html", global: false,// 禁用全局Ajax事件. // ... });
下面是jQuery官方给出的完整的Ajax事件列表:
- ajaxStart (Global Event)
This event is broadcast if an Ajax request is started and no other Ajax requests are currently running.- beforeSend (Local Event)
This event, which is triggered before an Ajax request is started, allows you to modify the XMLHttpRequest object (setting additional headers, if need be.) - ajaxSend (Global Event)
This global event is also triggered before the request is run. - success (Local Event)
This event is only called if the request was successful (no errors from the server, no errors with the data). - ajaxSuccess (Global Event)
This event is also only called if the request was successful. - error (Local Event)
This event is only called if an error occurred with the request (you can never have both an error and a success callback with a request). - ajaxError (Global Event)
This global event behaves the same as the local error event. - complete (Local Event)
This event is called regardless of if the request was successful, or not. You will always receive a complete callback, even for synchronous requests. - ajaxComplete (Global Event)
This event behaves the same as the complete event and will be triggered every time an Ajax request finishes.
- beforeSend (Local Event)
- ajaxStop (Global Event)
This global event is triggered if there are no more Ajax requests being processed.具体的全局事件请参考API文档。
好了,下面开始说jQuery里面功能最强的Ajax请求方法 $.ajax();jQuery.ajax( options ) : 通过 HTTP 请求加载远程数据
这个是jQuery 的底层 AJAX 实现。简单易用的高层实现见 $.get, $.post 等。
$.ajax() 返回其创建的 XMLHttpRequest 对象。大多数情况下你无需直接操作该对象,但特殊情况下可用于手动终止请求。
注意: 如果你指定了 dataType 选项,请确保服务器返回正确的 MIME 信息,(如 xml 返回 "text/xml")。错误的 MIME 类型可能导致不可预知的错误。见 Specifying the Data Type for AJAX Requests 。
当设置 datatype 类型为 'script' 的时候,所有的远程(不在同一个域中)POST请求都回转换为GET方式。$.ajax() 只有一个参数:参数 key/value 对象,包含各配置及回调函数信息。详细参数选项见下。
jQuery 1.2 中,您可以跨域加载 JSON 数据,使用时需将数据类型设置为 JSONP。使用 JSONP 形式调用函数时,如 "myurl?callback=?" jQuery 将自动替换 ? 为正确的函数名,以执行回调函数。数据类型设置为 "jsonp" 时,jQuery 将自动调用回调函数。(这个我不是很懂)
参数列表:
参数名 类型 描述 url String (默认: 当前页地址) 发送请求的地址。 type String (默认: "GET") 请求方式 ("POST" 或 "GET"), 默认为 "GET"。注意:其它 HTTP 请求方法,如 PUT 和 DELETE 也可以使用,但仅部分浏览器支持。 timeout Number 设置请求超时时间(毫秒)。此设置将覆盖全局设置。 async Boolean (默认: true) 默认设置下,所有请求均为异步请求。如果需要发送同步请求,请将此选项设置为 false。注意,同步请求将锁住浏览器,用户其它操作必须等待请求完成才可以执行。 beforeSend Function 发送请求前可修改 XMLHttpRequest 对象的函数,如添加自定义 HTTP 头。XMLHttpRequest 对象是唯一的参数。
function (XMLHttpRequest) { this; // the options for this ajax request }
cache Boolean (默认: true) jQuery 1.2 新功能,设置为 false 将不会从浏览器缓存中加载请求信息。 complete Function 请求完成后回调函数 (请求成功或失败时均调用)。参数: XMLHttpRequest 对象,成功信息字符串。
function (XMLHttpRequest, textStatus) { this; // the options for this ajax request }
contentType String (默认: "application/x-www-form-urlencoded") 发送信息至服务器时内容编码类型。默认值适合大多数应用场合。 data Object,
String发送到服务器的数据。将自动转换为请求字符串格式。GET 请求中将附加在 URL 后。查看 processData 选项说明以禁止此自动转换。必须为 Key/Value 格式。如果为数组,jQuery 将自动为不同值对应同一个名称。如 {foo:["bar1", "bar2"]} 转换为 '&foo=bar1&foo=bar2'。 dataType String 预期服务器返回的数据类型。如果不指定,jQuery 将自动根据 HTTP 包 MIME 信息返回 responseXML 或 responseText,并作为回调函数参数传递,可用值:
"xml": 返回 XML 文档,可用 jQuery 处理。
"html": 返回纯文本 HTML 信息;包含 script 元素。
"script": 返回纯文本 JavaScript 代码。不会自动缓存结果。
"json": 返回 JSON 数据 。
"jsonp": JSONP 格式。使用 JSONP 形式调用函数时,如 "myurl?callback=?" jQuery 将自动替换 ? 为正确的函数名,以执行回调函数。
error Function (默认: 自动判断 (xml 或 html)) 请求失败时将调用此方法。这个方法有三个参数:XMLHttpRequest 对象,错误信息,(可能)捕获的错误对象。
function (XMLHttpRequest, textStatus, errorThrown) { // 通常情况下textStatus和errorThown只有其中一个有值 this; // the options for this ajax request }
global Boolean (默认: true) 是否触发全局 AJAX 事件。设置为 false 将不会触发全局 AJAX 事件,如 ajaxStart 或 ajaxStop 。可用于控制不同的Ajax事件 ifModified Boolean (默认: false) 仅在服务器数据改变时获取新数据。使用 HTTP 包 Last-Modified 头信息判断。 processData Boolean (默认: true) 默认情况下,发送的数据将被转换为对象(技术上讲并非字符串) 以配合默认内容类型 "application/x-www-form-urlencoded"。如果要发送 DOM 树信息或其它不希望转换的信息,请设置为 false。 success Function 请求成功后回调函数。这个方法有两个参数:服务器返回数据,返回状态
function (data, textStatus) { // data could be xmlDoc, jsonObj, html, text, etc... this; // the options for this ajax request }
这里有几个Ajax事件参数:beforeSend ,success ,complete ,error 。我们可以定义这些事件来很好的处理我们的每一次的Ajax请求。注意一下,这些Ajax事件里面的 this 都是指向Ajax请求的选项信息的(请参考说 get() 方法时的this的图片)。
请认真阅读上面的参数列表,如果你要用jQuery来进行Ajax开发,那么这些参数你都必需熟知的。示例代码,获取博客园首页的文章题目:
$.ajax({ type: "get", url: "http://www.cnblogs.com/rss", beforeSend: function(XMLHttpRequest){ //ShowLoading(); }, success: function(data, textStatus){ $(".ajax.ajaxResult").html(""); $("item",data).each(function(i, domEle){ $(".ajax.ajaxResult").append("<li>"+$(domEle).children("title").text()+"</li>"); }); }, complete: function(XMLHttpRequest, textStatus){ //HideLoading(); }, error: function(){ //请求出错处理 } });
这里将显示首页文章列表。
其他
jQuery.ajaxSetup( options ) : 设置全局 AJAX 默认选项。
设置 AJAX 请求默认地址为 "/xmlhttp/",禁止触发全局 AJAX 事件,用 POST 代替默认 GET 方法。其后的 AJAX 请求不再设置任何选项参数。
jQuery 代码:
$.ajaxSetup({ url: "/xmlhttp/", global: false, type: "POST" }); $.ajax({ data: myData });
serialize() 与 serializeArray()
serialize() : 序列表表格内容为字符串。
serializeArray() : 序列化表格元素 (类似 '.serialize()' 方法) 返回 JSON 数据结构数据。
示例:
HTML代码:
<p id="results"><b>Results: </b> </p> <form> <select name="single"> <option>Single</option> <option>Single2</option> </select> <select name="multiple" multiple="multiple"> <option selected="selected">Multiple</option> <option>Multiple2</option> <option selected="selected">Multiple3</option> </select><br/> <input type="checkbox" name="check" value="check1"/> check1 <input type="checkbox" name="check" value="check2" checked="checked"/> check2 <input type="radio" name="radio" value="radio1" checked="checked"/> radio1 <input type="radio" name="radio" value="radio2"/> radio2 </form>
serializeArray() 结果为:
一些资源
一个jQuery的Ajax Form表单插件:http://www.malsup.com/jquery/form/
一个专门生成Loading图片的站点:http://ajaxload.info/ 大家觉得那些Loading比较炫的可以在这里跟帖晒一下,方便大家取用,嘎嘎
对于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回调处理返回数据。
一、生成xhr的时候,根据浏览器类型的不同,使用不同的方式创建:
源码:
//创建XMLHttpRequest对象 function createStandardXHR() { try { return new window.XMLHttpRequest(); } catch( e ) {} } //在IE6,7下的创建XMLHttpRequest function createActiveXHR() { try { return new window.ActiveXObject( "Microsoft.XMLHTTP" ); } catch( e ) {} } //创建请求对象 jQuery.ajaxSettings.xhr = window.ActiveXObject ? function() { return !this.isLocal && createStandardXHR() || createActiveXHR(); } : createStandardXHR;//对于除IE外的浏览器
二、Ajax方法的参数准备:
AJAX参数详细列表:
1>>AJAX请求设置。所有选项都是可选的
2>>async(Boolean):默认异步请求,如果同步 设置为false 用户其他操作在请求完成后才能执行
3>>beforeSend(function):发送请求前可以修改XMLHttpRequest唯一参数对象,
4>>complete(function):请求完成后的回调函数 参数:XMLHttpRequest对象和一个描述成功请求类型的字符串
5>>contentType(string):发送信息到服务器的内容编码类型
6>>data(object,string):发送到服务器的数据,将自动装换成请求字符串 GET请求中负载URL后 为数组 自动为值对应同一名称
7>>dataFilter(function):给Ajax返回的原始数据进行预处理的 函数,提供data和type两个参数,data是ajax返回的原始数据 type是dataType
8>>dataType(string):预期服务器返回的数据类型 如果不确定 将返回responseXML或responseText
9>>responseText:xml html script json jsonp text
10>>error(function):失败回调函数 XMLHttpRequest对象 ,错误信息 捕获的错误对象
11>>global(boolean):是否全局
12>>ifModified(boolean):仅在服务器数据改变时获取新数据 HTTP Last-Modified
//$.ajaxSetup({}) 设置自己的ajax参数,如果target不存在 把$.ajaxSettings默认的参数扩展到target //最后都是通过settings参数扩展target参数 ajaxSetup: function ( target, settings ) { //如果只有一个参数 if ( !settings ) { settings = target; //将target的值赋给settings,来扩展ajaxSettings,然后将ajaxSettings赋值给target target = jQuery.extend( true, jQuery.ajaxSettings, settings ); } else { //用ajaxSettings和settings扩展traget jQuery.extend( true, target, jQuery.ajaxSettings, settings );//扩展 } //这个for怎么理解? for( var field in { context: 1, url: 1 } ) { //字段在settings中 if ( field in settings ) { target[ field ] = settings[ field ]; //字段在jQuery.ajaxSettings中 } else if( field in jQuery.ajaxSettings ) { target[ field ] = jQuery.ajaxSettings[ field ]; } } return target; }, //ajax默认设置,可以通过$.ajaxSetup进行设置 ajaxSettings: { url: ajaxLocation,//请求的URL isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),//本地文件 global: true,//是否触发全局ajax事件,设置为false将不会触发ajax事件如:ajaxStart 或sjaxStop type: "GET", contentType: "application/x-www-form-urlencoded",//form形式 processData: true,//在默认情况下,发送的数据将被转换为对象以配合默认内容类型。如果要发送DOM树信息或其他不希望转换的信息 请设置false async: true,//是否异步 注意,同步请求将锁住浏览器,用户其它操作必须等待请求完成才可以执行。 /* timeout: 0,//设置请求超时时间 data: null,//传递的数据 dataType: null, //html, json, jsonp, script, or text. username: null, password: null, cache: null,//是否缓存文件 traditional: false, headers: {}, */ //接受文件头信息 accepts: { xml: "application/xml, text/xml", html: "text/html", text: "text/plain", json: "application/json, text/javascript", "*": "*/*" }, contents: { xml: /xml/, html: /html/, json: /json/ }, //返回文件类型 responseFields: { xml: "responseXML", text: "responseText" }, //可以进行类型装换的 用于ajax的类型转换器 converters: { // Convert anything to text "* text": window.String, // Text to html (true = no transformation) "text html": true, // 文本转换成json "text json": jQuery.parseJSON, // 文本转换成XML "text xml": jQuery.parseXML } },
接下来就是实现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回调处理返回数据。
这个真心有点长啊!
ajax: function( url, options ) { //$.ajax({url:'xx',data:{},}); //如果url是对象的话 修正参数 if(typeof url == "object"){ options = url; options = undefined; } //确保options是对象,不会产生undefined options = options || {}; //创建ajax的很多私有变量, 可以通过ajaxSetup改变 var s = jQuery.ajaxSetup({},options);//创建最终的ajax参数对象s //回调函数的上下文,如果有context就是s.context,否则是s s.context是什么? 传进去的上下文属性 callbackContext = s.context || s; //事件作用域: 如果是DOM node 或者 jQuery collection ?? //回调函数作用域不是s DOM节点或回调函数作用域是jQuery的实例 ? jQuery的回调函数上下文 : 事件 globalEventContext = callbackContext !== s && (callbackContext.nodeType || callbackContext instanceof jQuery) ? jQuery(callbackContext) : jQuery.event, //异步队列: deferred = jQuery.Deferred(), completeDeferred = jQuery._Deferred(), //一组数值的HTTP代码和函数对象 $.ajax({statusCode:{404:function(){alert('page no found');};}}); 默认为空对象 statusCode = s.statusCode || {}, ifModifiedKey, //请求头部 requestHeaders = {}, requestHeadersNames = {}, //响应headers responseHeadersString,//响应头部字符串 responseHeaders,//响应头部 transport, //请求超时时间,异步请求下请求多少时间后终止请求 timeoutTimer, //判断是否是跨域请求的变量 parts, //jqXHR state state =0, //是否全局事件 fireGlobals, //loop variable i, jqXHR = { /* readyState五种状态: onreadystatechange回调函数 0>>uninitialized:未初始化 未调用open() 1>>Loading:已调用open() 未调用send()方法正在发送请求 2>>loaded:已经调用send()当尚未收到响应 3>>interactive:正在解析响应内容,已经接收到部分响应内容 4>>completed:完成 可以在客户端调用了 */ readyState:0,//初始状态 //设置dataType,达到预期服务器返回的数据类型 jQuery将自动根据HTTP的MIME包信息判断 name:头部字段名称 value:头部字段值 //设置请求头部,示例:xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded"); setRequestHeader:function(name,value){ //这里state = 0,发送请求前,设置请求头 if(!state){ var lname = name.toLowerCase(); name = requestHeadersNames[lname] = requestHeadersNames[lname] || name; requestHeaders[name] = value;//私有requestHeadersNames } return this; }, /* 原生字符串,取的是包含所有头部信息的长字符串 getAllResponse-Headers(): 返回:Date: , server:Appache/1.3.39(Unix), Vary:Accept, X-Powered-By:PHP, Connection:close, Content-Type:text/html;charset =utf-8; */ getAllResponseHeaders:function(){ //state === 2,已经调用send()当尚未收到响应 return state === 2 ? responseHeadersString : null; }, //传入头部字段名称,取得相应的响应头部信息 getResponseHeader:function(key){ var match; if(state ===2){ if(!responseHeaders){ responseHeaders = {}; while ((match = rheaders.exec(responseHeadersString))){ responseHeaders[match[1].toLowerCase()] = match[2]; } } //获得Headers内容value match = responseHeaders[key.toLowerCase()]; } return match === undefined ? null : match; }, //overrides(重写) response context-type header mimeType /*用法 在beforeSend中可以用 $.ajax({ url: "http://fiddle.jshell.net/favicon.png", beforeSend: function ( xhr ) { xhr.overrideMimeType("text/plain; charset=x-user-defined"); } }).done(function ( data ) { if( console && console.log ) { console.log("Sample of data:", data.slice(0, 100)); } }); */ //将资源的媒体类型 设置成传递进来的 overrideMimeType:function(type){//重写MimeType if(!state){ s.mimeType = type;//数据类型 将资源的媒体类型 } return this; }, // cancel the request 中断请求 abort:function(statusText){ statusText = statusText || "abort"; if(tarnsport){ transport.abort(statusText);//xhr.abort();不在允许访问任何响应有关的属性 } done(0,statusText);//调用下面的 return this; } }; //callback for when everything is done /* 2:request received ---->readyState 4:request finished and response is ready 200:"OK" 404:page not found --->status 1.清除本次请求用到的变量 2.解析状态码&状态描述 3.执行异步回调函数队列 4.执行complete队列 5.触发全局ajax status -1 没有找到请求分发器 闭包函数done 在done中判断本次请求是否成功,如果成功就调用ajaxConvert对响应的数据进行类型转换 */ /************************************ done()处理不同状态的request **************************************/ function done( status, statusText, responses, headers ) {//处理不同状态的request //表示已经请求过一次,立即返回,第一次请求默认的state=0 if ( state === 2 ) { return; } //正在请求 //state is "done" now state = 2;//因为XMLHttpRequest的 2 是request received //如果存在超时请求,清除这个超时时间 if ( timeoutTimer ) { clearTimeout( timeoutTimer ); } transport = undefined;//中止传输 为了垃圾回收 // Cache response headers 缓存请求头部 responseHeadersString = headers || "";//响应头部字符串 headers:函数传递进来的 // Set readyState jqXHR.readyState = status ? 4 : 0; var isSuccess, success, error, //ajaxHandleResponses()是做什么的? response = responses ? ajaxHandleResponses( s, jqXHR, responses ) : undefined, lastModified, etag; //status:响应的HTTP状态 200:成功标志 304 请求资源并没有修改,可以读缓存中的数据 //statusText:HTTP状态说明 //responseText:对于非XML,内容保存在responseText中 responseXML为空 //如果成功 处理类型 状态是200 if ( status >= 200 && status < 300 || status === 304 ) { //检查服务器端的文件是否被改变 改变了处理返回的头部信息,获取必要的信息即可 if ( s.ifModified ) { if ( ( lastModified = jqXHR.getResponseHeader( "Last-Modified" ) ) ) { jQuery.lastModified[ ifModifiedKey ] = lastModified; } if ( ( etag = jqXHR.getResponseHeader( "Etag" ) ) ) { jQuery.etag[ ifModifiedKey ] = etag; } } //服务器内容没有发生改变,修改状态数据,设置成功 if ( status === 304 ) { statusText = "notmodified";//空的响应体 和 304 isSuccess = true; } else {// If we have data try { //---------------------------------------------------------------------------------- //------------------------ 类型转换器:转换请求的数据类型,(XML,json,js) ------------------ //---------------------------------------------------------------------------------- //类型转换器,转换成指定的文件类型 将server返回的数据进行相应的转化(js json等) success = ajaxConvert( s, response );//这里的sucess变为转换后的数据对象 statusText = "success"; isSuccess = true;//200状态 isSucess = true } catch(e) { //数据类型转换器解析时出错 statusText = "parsererror"; error = e; } } //非200-300,也非304 } else { //从statusText中提取error 标准化 statusText和status 其他的异常状态 格式化statusText status,不采用标准的HTTP码 error = statusText; if( !statusText || status ) { statusText = "error"; if ( status < 0 ) { status = 0; } } } //把状态参数和状态内容赋给jqXHR jqXHR.status = status;//设置jqXHR.status 其实也就是XMLHttpRequest状态 jqXHR.statusText = statusText; //获取成功与失败的回调函数 if ( isSuccess ) { //回调上下文和参数 deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); } else { deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); } //$.ajax({statusCode:{404:function(){alert('page no found');};}}); //获得当前的状态码回调函数,函数在下面 jqXHR.statusCode( statusCode );//状态参数代码 默认为空 statusCode = undefined; //是否全局事件 ,$(selector).trigger() 触发被选元素的指定事件类型 if ( fireGlobals ) { globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ), [ jqXHR, s, isSuccess ? success : error ] ); } //complete() 函数。 resolveWith completeDeferred.resolveWith( callbackContext, [ jqXHR, statusText ] ); if ( fireGlobals ) { globalEventContext.trigger( "ajaxComplete", [ jqXHR, s] ); //全局ajax计数器 if ( !( --jQuery.active ) ) { jQuery.event.trigger( "ajaxStop" ); } } } /************************************ End done() **************************************/ //给jqXHR对象赋上deferred对象的只读方法,包括done,alwaysis,Resolved deferred.promise( jqXHR ); jqXHR.success = jqXHR.done; jqXHR.error = jqXHR.fail; jqXHR.complete = completeDeferred.done; //获取当前的status状态码的回调函数 jqXHR.statusCode = function( map ) {//map={200:"..",0:".."} if ( map ) { var tmp;//临时变量 if ( state < 2 ) {//请求还没回调 for( tmp in map ) { statusCode[ tmp ] = [ statusCode[tmp], map[tmp] ];//键值对 } } else {//请求完成阶段 ,jQuery返回相应的callback,then 调用 tmp = map[ jqXHR.status ]; jqXHR.then( tmp, tmp );//我们的状态代码在这里调用,通过jqXHR.then方法调用 } } return this; }; // Remove 将url的hash去掉 rhash=/#.*$/ rprotocol = /^\/\// ajaxLocParts[1]="http:" ajaxLocParts[ 1 ] + "//"也就是http:// s.url = ( ( url || s.url ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" ); //取出dataType,不存在为 * 去空格,然后以空格拆分数据类型 rspacesAjax = /\s+/ 如:s.dataType=[*] s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( rspacesAjax ); //设置crossDomain ,同域为false,跨域为true 如果想强制跨域请求(如JSONP形式)同一域,设置crossDomain为true if ( s.crossDomain == null ) { //同ajaxLocParts变量,这里也是通过这个请求的url来判断是否是跨域请求。rurl是将url拆分 parts = rurl.exec( s.url.toLowerCase() ); //这里来判断是否是跨域请求,又是!! 主要判断请求的url的parts与 ajaxLocParts 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 ) ) ) ); } //生成加到字符串后面的参数字符串 ?号后面的 序列化传递的参数 processData:请求进度 //如果存在data参数并且 processData=true&&s.data为一个对象 if ( s.data && s.processData && typeof s.data !== "string" ) { s.data = jQuery.param( s.data, s.traditional );//序列化参数 } //---------------------------------------------------------------------------------- //------------------发送请求前,调用前置过滤器-------------------------------------------- //---------------------------------------------------------------------------------- inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); //前置过滤器中中止了 停止请求 if ( state === 2 ) { return false; } //判断是否触发全局事件 fireGlobals = s.global; //将状态码改为大写 请求的方式 s.type = s.type.toUpperCase(); //请求方式是否变化 s.hasContent = !rnoContent.test( s.type );//状态码是否有内容,是否变化 没变化说明没有修改 //一个新的请求 ajaxStart 开始 if ( fireGlobals && jQuery.active++ === 0 ) { jQuery.event.trigger( "ajaxStart" ); } // More options handling for requests with no content if ( !s.hasContent ) { if ( s.data ) { //这就是为什么data参数会被传到url ?号 后面 //判断是否有问号,有酒用 &链接参数 没有就用 ? s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data; } // Get ifModifiedKey before adding the anti-cache parameter ifModifiedKey = s.url;//获得ifModifiedKey 读取最后修改时间判定 //没有缓存内容 s.cache是否存在 if ( s.cache === false ) { var ts = jQuery.now(), //用当前时间替换掉原来的最后修改时间 ret = s.url.replace( rts, "$1_=" + ts ); //如果没变,将时间戳加到后面 s.url = ret + ( (ret === s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : "" ); } } //设置header if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { jqXHR.setRequestHeader( "Content-Type", s.contentType ); } //请求的内容是否改变 if ( s.ifModified ) { //请求的关键字就是 :请求的url ifModifiedKey = ifModifiedKey || s.url; //http://www.cnblogs.com/czh-liyu/archive/2011/06/22/2087113.html if ( jQuery.lastModified[ ifModifiedKey ] ) { //最后修改时间 jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ ifModifiedKey ] ); } if ( jQuery.etag[ ifModifiedKey ] ) { //任意属性 jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ ifModifiedKey ] ); } } /* 设置dataType,达到预期服务器返回的数据类型,如果没有dataType参数,jQuery 将自动根据 HTTP 包 MIME 信息来智能判断 默认的 s.accepts= accepts:{ xml: "application/xml, text/xml", html: "text/html",text: "text/plain", json: "application/json, text/javascript", "*": allTypes } allTypes= ['*\/']+['*'] 如果设置了dataType参数,即 s.dataTypes = jQuery.trim(s.dataType || "*").toLowerCase().split(/\s+/); 比如:$(url,{dataType:"json"}) 这里第二个参数将成为 "application/json, text/javascript"+",*\/*;q=0.01" */ //Set the Accepts header for the server, depending on the dataType //浏览器能够处理的内容类型 jqXHR.setRequestHeader( "Accept", s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ? s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", */*; q=0.01" : "" ) : s.accepts[ "*" ] ); //检查头部是否设置了参数 s.headers={} 一个额外的"{键:值}"对映射到请求一起发送 -->没设置头部 for ( i in s.headers ) { jqXHR.setRequestHeader( i, s.headers[ i ] ); } //发送请求前的拦截函数,确保满足条件 if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) { jqXHR.abort();//中止请求 禁止访问属性 return false; } //$.ajax().success(callback).error(callback).complete(callback)在这里增加callback 这里表面现在ajax请求可以不止一个回调函数 for ( i in { success: 1, error: 1, complete: 1 } ) { jqXHR[ i ]( s[ i ] ); } //---------------------------------------------------------------------------------- //------------------------------请求分发器-------------------------------------------- //---------------------------------------------------------------------------------- transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); //如果没有请求分发器自动终止 if ( !transport ) { done( -1, "No Transport" ); } else { jqXHR.readyState = 1; // Send global event 发送全局的事件 if ( fireGlobals ) { globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); } //Timeout设置请求超时时间(毫秒),超时请求后,jqXHR.abort() 中止请求,简单的意思就是在指定的时间内还未响应,停止请求 if ( s.async && s.timeout > 0 ) { timeoutTimer = setTimeout( function(){ jqXHR.abort( "timeout" ); }, s.timeout ); } try { state = 1; //发送请求头部 调用transport.send()方法 transport.send( requestHeaders, done ); } catch (e) { // Propagate exception as error if not done if ( status < 2 ) { done( -1, e ); } else { jQuery.error( e ); } } } return jqXHR; }, //序列化参数 param: function( a, traditional ) { var s = [],//空数组 add = function( key, value ) {//声明方法 value = jQuery.isFunction( value ) ? value() : value; s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value ); }; if ( traditional === undefined ) {//没设定就调用默认的 traditional = jQuery.ajaxSettings.traditional; } //数组 if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { jQuery.each( a, function() { //遍历a中的name 和 value add( this.name, this.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 { for ( var prefix in a ) { buildParams( prefix, a[ prefix ], traditional, add ); } } return s.join( "&" ).replace( r20, "+" ); } });
这中间用到了异步队列调用和前置过滤器、分发器以及类型转换器。异步队列独立在前面的文章中了。
前置过滤器:
* undefined 不做任何处理 事实上也没有 * 属性
json function 被当做 * 处理
jsonp function 修正url或data 增加回调函数 在window上注册回调函数 注册script > json数据转换器
scirpt fucntion 设置以下参数:是否缓存 cache 如果跨域 请求类型 如果跨域 是否触发AJAX全局事件
调用jQuery.ajaxPrefilter填充prefilters 过滤器
Detect, normalize options and install callbacks for jsonp requests
向前置过滤器对象中添加特定类型的过滤器
添加过滤器将格式化参数 并且为jsonp请求增加callbacks
MARK:AJAX模块初始化
源码分析:
jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) { //如果是表单提交,检查数据是字符串 var inspectData = s.contentType === "application/x-www-form-urlencoded" && (typeof s.data === "string"); //这个方法只处理jsonp,如果json的url或data有jsonp的特征被当成jsonp处理 //触发jsonp的3种方式 if(s.dataTypes[0] === "jsonp" ||//如果是jsonp s.jsonp !== false && (jsre.test(s.url) ||//为禁止jsopn, s.url中包含=?& =?$ ?? inspectData && jsre.test(s.data))){//s.data中包含=?& =?$ ?? var responseContainer, //如果s.jsonpCallback是函数 执行s.jsonpCallback作为函数名的回调函数 //回调函数 jsonpCallback = s.jsonpCallback = jQuery.isFunction(s.jsonpCallback) ? s.jsonpCallback() : s.jsonpCallback, previous = window[jsonpCallback],//window上的函数 url = s.url, data = s.data, //jsre = ;//=?& =?$ ?? repalce = "$1" + jsonpCallback + "$2";//$1=,$2 &|$ 加入时间戳 if(s.jsopn !== false){ url = url.replace(jsre,replace);//将回调函数名插入url if(s.url === url){//如果url没有变化,尝试修改data if(inspectData){//数据类型 data = data.replace(jsre,replace);//将回调函数插入data } if(s.data === data){//如果data也咩有变化 url += (/\?/.test(url) ? "&" : "?") +s.jsonp + "=" + jsonpCallback; //自动在url后面添加回调函数 } } } //存储可能改变过的url data /* 修正url 和 data */ s.url = url; s.data = data; //install callback /* 在window上注册回调函数 */ window[jsonpCallback] = function(response){//在window上注册回调函数 responseContainer = [response]; }; //clean-up function jqXHR.always(funciton(){ //将备份的previous函数恢复 window[jsonpCallback] = previous; //响应完成时调用的jsonp回调函数 if(responseContainer && jQuery.isFunction(previous)){ window[jsonpCallback](responseContainer[0]); } }); s.converters["script json"] = function(){ if(!responseContainer){ jQUery.error(jsonpCallback + "was not called"); } return responseConteainer[0];//作为方法的参数传入,本身就是一个json对象不需要再做转换 }; /* 注册scirpt>json 数据转换器 */ s.dataTypes[0] = "json";//强制为json return "script";//jsonp > json } }); //设置script的前置过滤器,script并不一定跨域 //设置以下参数:是否缓存 cache (如果跨域) 请求类型 (如果跨域) 是否触发AJAX全局事件 jQuery.ajaxPrefilter( "script", function( s ) { if(s.cache == undefined){//如果缓存为设置 定义为false s.cache = false; } if(s.crossDomain){//如果跨域未被禁用 GET 不触发全局事件 s.type = "GET"; s.global = false; } });
请求分发器:
* function:返回xhr分发器 分发器带有send,abort方法 send方法依次调用open send方法发送请求并绑定onreadystatechange事件句柄
script function:返回script分发器 担忧send abort方法 send方法通过在header中创建scirpt标签异步载入js
并在scirpt元素上绑定onload onreadystatechange事件句柄
jQuery.ajaxTransport填充taransports 分发器
Bind script tag hack transport open() 和 send()方法
源码分析:
jQuery.ajaxTransport( "script", function(s) { if(s.crossDomain){//script 可能是json 或 jsonp jsonp需要跨域 //如果在本域中设置了跨域会怎么处理 ? var script, head = document.head || document.getElementsByTagName("head")[0] || document.documentElement;//利用布尔表达式计算顺序 return { /*以下即是异步加载js的常用方法*/ send:function(_,callback){//提供与同域请求一致的接口 script = document.createElement("script");//通过创script标签来实现 script.async = "async"; } if(s.scirptCharset){ scirpt.charset = s.scriptCharset;//字符集 } script.src = s.url;//动态载入 // script加载完成触发的callback script.onload = scirpt.onreadystatechange = function(_,isAbort){//js文件加载后的callback if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) { //处理ie下得内存泄露问题 ,onload事件触发后,销毁事件. script.onload = script.onreadystatechange = null; if ( head && script.parentNode ) { //加载过后删除script标签元素 head.removeChild( script ); } //注销script script = undefined; if ( !isAbort ) { callback( 200, "success" );//执行回调函数 200为HTTP状态码 } } }; //把创建的script加入head头部,但是加载完成后会被清除该script标签,用insertBefore代替appendChild,如果IE6 有bug head.insertBefore( script, head.firstChild ); }, abort: function() { if ( script ) { //手动触发onload时间 jqXHR状态码为0 HTTP状态码为1XX script.onload( 0, 1 ); } } }; } });
addToPrefiltersOrTransports():
首先,prefilters和transports被置为空对象:添加全局前置过滤器或请求分发器,过滤器在发送之前调用, 分发器用于区分ajax请求和script标签请求
prefilters = {}, // 过滤器
transports = {}, // 分发器
然后,创建jQuery.ajaxPrefilter和jQuery.ajaxTransport,这两个方法都调用了内部函数addToPrefiltersOrTransports,
addToPrefiltersOrTransports返回一个匿名闭包函数,这个匿名闭包函数负责将单一前置过滤和单一请求分发器分别放入prefilters和transports。
我们知道闭包会保持对它所在环境变量的引用,而jQuery.ajaxPrefilter和jQuery.ajaxTransport的实现又完全一样,都是对Map结构的对象进行赋值操作,
因此这里利用闭包的特性巧妙的将两个方法的实现合二为一。函数addToPrefiltersOrTransports可视为模板模式的一种实现。
源码分析:
// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport //1>>过滤掉 + 开头的 2>>分发器用于区分ajax请求和script标签请求 function addToPrefiltersOrTransports( structure ) { //返回一个闭包,通过闭包访问structure,关键在于structure引用哪个对象:能同时引用prefilters和transports //dataTypeExpression是可选的 默认为 * 数据类型表达式 return function(dataTypeExpression,func){ //修正参数 dataTypeExpression部位字符串默认为 * if (typeof dataTypeExpression !== "string"){ func = dataTypeExpression; dataTypeExpression = "*"; } if(jQuery.isFunction(func){ // 用空格分割数据类型表达式dataTypeExpression var dataTypes = dataTypeExpression.toLowerCase().split(rspacesAjax), i = 0, length = dataTypes.length,//数据类型表达式长度 dataType,//单个字段 数据类型表达式 list, placeBefore; for (;i<length;i++){ dataType = dataTypes[i]; //如果以+开头 过滤掉 + palceBefore = /^\+/.test(dataType);//选出加号开头的dataType if(palceBefore){ dataType = dataType.substr(1) || "*";//从第二个开始或者 * } list = structure[dataType] = structure[dataType] || []; //如果以+ 开头,则插入开始位置 否则添加到末尾 //实际操作的是structure list[placeBefore ? "unshift" : "push" ](func);//将函数push进structure } } }; }
//prefilters中的前置过滤器在请求发送前,设置请求参数的过程中被调用 调用prefilters的是inspectPrefiltersOrTransports
//transports也通过这个函数取到与请求类型匹配的请求分发器 inspectPrefiltersOrTransports从prefilters或transports中取到与数据类型匹配的函数数组 然后遍历执行
源码分析:
function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR, dataType /* internal */, inspected /* internal */ ) { dataType = dataType || options.dataTypes[ 0 ]; inspected = inspected || {}; inspected[ dataType ] = true; var list = structure[ dataType ], i = 0, length = list ? list.length : 0, executeOnly = ( structure === prefilters ), selection; for(; i < length && ( executeOnly || !selection ); i++ ) { selection = list[ i ]( options, originalOptions, jqXHR );//遍历数组 // If we got redirected to another dataType // we try there if executing only and not done already if ( typeof selection === "string" ) { if ( !executeOnly || inspected[ selection ] ) { selection = undefined; } else { options.dataTypes.unshift( selection ); selection = inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR, selection, inspected ); } } } // If we're only executing or nothing was selected // we try the catchall dataType if not done already if ( ( executeOnly || !selection ) && !inspected[ "*" ] ) { selection = inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR, "*", inspected ); } // unnecessary when only executing (prefilters) // but it'll be ignored by the caller in that case return selection; }
类型转换器:
* text window.String 任意内容转换成字符串
text html true 文本转换成THML
text json jQuery.parseJSON 文本转换成JSON
text script globalEval(text); 用eval执行text
text xml jQuery.parseXML 文本转换为XML
类型转换器 将responseText或responseXML转换成请求时指定的数据类型dataType
如果没有指定就一句响应头Content-Type自动猜测一个
转换过程是:如果A>B 首先查找 A>B ,如果没有找到 查找C 将A转换成C 然后将C再转换成B
源码分析:
function ajaxConvert( s, response ) { // Apply the dataFilter if provided if ( s.dataFilter ) { response = s.dataFilter( response, s.dataType ); } var dataTypes = s.dataTypes, converters = {}, i, key, length = dataTypes.length, tmp, // Current and previous dataTypes current = dataTypes[ 0 ], prev, // Conversion expression conversion, // Conversion function conv, // Conversion functions (transitive conversion) conv1, conv2; // For each dataType in the chain for( i = 1; i < length; i++ ) { // Create converters map // with lowercased keys if ( i === 1 ) { for( key in s.converters ) { if( typeof key === "string" ) { converters[ key.toLowerCase() ] = s.converters[ key ]; } } } // Get the dataTypes prev = current; current = dataTypes[ i ]; // If current is auto dataType, update it to prev if( current === "*" ) { current = prev; // If no auto and dataTypes are actually different } else if ( prev !== "*" && prev !== current ) { // Get the converter conversion = prev + " " + current; conv = converters[ conversion ] || converters[ "* " + current ]; // If there is no direct converter, search transitively if ( !conv ) { conv2 = undefined; for( conv1 in converters ) { tmp = conv1.split( " " ); if ( tmp[ 0 ] === prev || tmp[ 0 ] === "*" ) { conv2 = converters[ tmp[1] + " " + current ]; if ( conv2 ) { conv1 = converters[ conv1 ]; if ( conv1 === true ) { conv = conv2; } else if ( conv2 === true ) { conv = conv1; } break; } } } } // If we found no converter, dispatch an error if ( !( conv || conv2 ) ) { jQuery.error( "No conversion from " + conversion.replace(" "," to ") ); } // If found converter is not an equivalence if ( conv !== true ) { // Convert with 1 or 2 converters accordingly response = conv ? conv( response ) : conv2( conv1(response) ); } } } return response; }
扩展生成的ajax方法:
jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split( " " ), function( i, o ){ jQuery.fn[ o ] = function( f ){ return this.bind( o, f );//生成的函数 AJAX 事件 }; }); //each 调用ajax实现get和post方法 jQuery.each( [ "get", "post" ], function( i, method ) { jQuery[ method ] = function( url, data, callback, type ) { //没有传入参数 if ( jQuery.isFunction( data ) ) { type = type || callback; callback = data; data = undefined; } return jQuery.ajax({ type: method, url: url, data: data, success: callback, dataType: type }); }; });
调用get方法获取js或json的方法:
//jQuery自带的加载js方法,只存在一次请求中,$.getScript("free.js",callback);只能在callback中使用js 且被压缩了 getScript: function( url, callback ) { return jQuery.get(url,undefined,callback,"script"); }, //引用JSON文件 getJSON: function( url, data, callback ) { return jQuery.get(url,undefined,callback,"script"); },
posted on 2013-04-11 17:21 color_story 阅读(796) 评论(0) 编辑 收藏 举报