Ajax是Adaptive Path的Jesse James Garrett(同时也是《用户体验要素》的作者)提出的一个名词。他在解释XMLHttpRequest对象宋进行的异步的客户端到服务器端通信时,使用了这一名词。作为异步JavaScript与XML(Asynchronous JavaScript and XML)的缩写,它只是创建动态Web应用程序必备技术的一个统称。基本的Ajax内容从具体的请求到最后的数据操作,它包括:
□ 分析不同类型的HTTP请求,决定以何种方式将数据对象发送到服务器最合适
□ 观察整个HTTP响应过程,尝试处理所有可能发生的错误,包括服务器的超时
□ 读取、遍历并操作服务器响应的结果数据
1. HTTP请求
Ajax中最重要也是最固定的部分是HTTP请求。归根结底,Ajax的目的是异步地向服务器发送数据并接收数据。至于数据是什么格式,取决于具体的文档。
建立连接
这部分主要设计如何格式化数据,以便使用不同的HTTP请求传输到服务器。还要解决如何与服务器建立基本的连接,以及在跨浏览器环境中处理这些的必要细节。
根据用户浏览器的不同,XMLHttpRequest对象的数据通信通常有两种方法实现:
(1)IE的ActiveXObject。IE7对XMLHttpRequest对象已经有了直接的支持
(2)其它所有的现代浏览器都直接支持XMLHttpRequest对象
一种向服务器发送HTTP GET请求的跨浏览器方法:
// 如果是IE if ( typeof XMLHttpRequest == "undefined" ) XMLHttpRequest = function(){ // IE使用 ActiveXObject return new ActiveXObject( // 区分IE5和IE6 navigator.userAgent.indexOf("MSIE 5") >= 0 ? "Microsoft.XMLHTTP" : "Msxml2.XMLHTTP" ); }; // 创建新的请求对象 var xml = new XMLHttpRequest(); // 初始化请求 xml.open("GET", "/some/url.cgi", true); // 与服务器连接并发送数据 xml.send();
Ajax方法中最重要的特性是允许数据在客户端和服务器端之间传输,有鉴于此,接下来讨论如何包装数据来发送到服务器。
数据的串行化
要发送一系列数据到服务器上,首先就是要整理它的格式,使服务器便于读取,这一过程称为“串行化(serialization)”。串行化有两种不同的情况,但都能满足多种不同的传输要求。
(1)传输常规的JavaScript对象,其中包括键/值的对
(2)一系列表单字段。这种情况元素是按顺序排列的,而第一种情况可以任意次序。
将原生JavaScript对象转化为串行形式的例子:
// 键/值对 { name: "Mark", last: "Zuckerberg", city: "Cambridge", zip: 02140 } // 串行形式 name=Mark&last=Zuckerberg&city=Cambridge&zip=02140 // 另一组数据 [ { name: "name", value: "Mark" }, { name: "last", value: "Zuckerberg" }, { name: "lang", value: "JavaScript" }, { name: "lang", value: "Perl" }, { name: "lang", value: "Java" } ] // 串行形式 name=Mark&last=Zuckerberg&lang=JavaScript&lang=Perl&lang=Java // 使用id方法生成的键/值对 [ id( "name" ), id( "last" ), id( "username" ), id( "password" ) ] // 将其串行化为字符串 name=Mark&last=Zuckerberg&username=jeresig&password=test
现在建立一个将上述数据串行化的标准方法:
// 串行化数据。支持两种不同的对象 // - 表单字段的数组 // - 键值对的哈希表 // 返回串行化的字符串 function serialize(a) { // 结果数组 var s = []; // 如果参数是数组,则假定他们是表单字段 if ( a.constructor == Array ) { // 串行化表单字段 for ( var i = 0; i < a.length; i++ ) s.push( a[i].name + "=" + encodeURIComponent( a[i].value ) ); // 否则,假定它是键值对 } else { // 串行化键值对 for ( var j in a ) s.push( j + "=" + encodeURIComponent( a[j] ) ); } // 返回串行化后的结果 return s.join("&"); } [/code] 现在数据都转成了串行形式的字符串,接下来使用GET或POST请求发送数据到服务器上 <strong>发送GET请求</strong> 一个跨浏览器的向服务器发送HTTP GET请求的方法 // 建立异步请求 var xml = new XMLHttpRequest(); // 初始化异步get请求 xml.open("GET", "/some/url.cgi?" + serialize( data ), true); // 与服务器建立连接 xml.send();
串行数据附在服务器url后面,用?字符分隔。
发送POST请求
使用XMLHttpRequest发送HTTP请求的另一种方式是POST,这种方式与GET方式完全不同。POST支持发送任意格式、任意长度的数据,而不仅局限与串行化字符串。用于发送串行数据的MIME类型(content type)通常是application/x-www-form-urlencoded。同样还可以以text/xml或application/xml的形式直接发送XML,甚至以application/json的形式发送JavaScript对象。
一种跨浏览器的向服务器发送HTTP POST请求的方法
// 建立异步请求 var xml = new XMLHttpRequest(); // 初始化异步post请求 xml.open("POST", "/some/url.cgi", true); // 设置 content-type,通知服务器如何解析发送的数据 xml.setRequestHeader( "Content-Type", "application/x-www-form-urlencoded"); // 保证浏览器发送的串行数据长度正确 if ( xml.overrideMimeType ) xml.setRequestHeader("Connection", "close"); // 与服务器建立连接,并发送串行化数据 xml.send( serialize( data ) );
将XML数据发送到服务器的例子:
// 建立异步请求 var xml = new XMLHttpRequest(); // 初始化异步post请求 xml.open("POST", "/some/url.cgi", true); // 设置 content-type,通知服务器如何解析发送的数据 xml.setRequestHeader( "Content-Type", "text/xml"); // 保证浏览器发送的串行数据长度正确 if ( xml.overrideMimeType ) xml.setRequestHeader("Connection", "close"); // 与服务器建立连接,并发送串行化数据 xml.send( "<items><item id='one'/><item id='two'/></items>" );
2. HTTP响应
使用XMLHttpRequest对象的优越之处在于,它能够从服务器读取不同格式的数据,不仅仅是XML,还可以是HTML或者JSON等。首先用一个简化的例子来演示处理服务器数据的步骤:
// 建立异步请求 var xml = new XMLHttpRequest(); // 初始化异步post请求 xml.open("GET", "/some/url.cgi", true); // 在文档的状态更新时调用 xml.onreadystatechange = function(){ // 数据完整加载 if ( xml.readyState == 4 ) { // xml.responseXML 包含XML文档(如果返回的是XML) // xml.responseText 包含文本(如果返回的不是XML) // 为避免内存泄漏,清理文档 xml = null; } }; // 与服务器建立连接 xml.send();
在这里,responseXML和responseText属性分别包含对应格式的数据。在对响应数据进行实际的处理、遍历和操作之前,首先创建一个更健壮的onreadystatechange函数,用来处理服务器错误和超时。
处理错误
处理错误情况需要检查以下状况:
□ 成功响应代码:通过HTTP规范里定义的响应状态码来检查错误。状态码在200到300之间的属于成功的请求
□ 未修改响应:服务器返回"Not Modified"标记,也就是状态码304。说明服务器返回的数据和浏览器的缓存内容一致,并未修改过
□ 本地文件:如果在本机上直接执行Ajax代码,而不通过Web服务器,就算请求成功了,也不会得到任何状态码。这意味着,在执行本地文件且得不到状态码时,应该把这种情况算作成功的响应
□ Safari与未修改状态:如果文档自上次请求未曾修改过,Safari返回的状态码会是"undefined"
用于检查服务器HTTP响应的成功状态函数:
// 检查 XMLHttpRequest 对象的状态码是否为 'Success' // 此函数需要一个 XMLHttpRequest 对象作为参数 function httpSuccess(r) { try { // 如果得不到服务器状态,且正在请求本地文件,则返回 true return !r.status && location.protocol == "file:" || // 200 至 300 之间的状态码,返回 true ( r.status >= 200 && r.status < 300 ) || // 文档为修改也返回 true r.status == 304 || // Safari在文档修改时返回空状态,返回true navigator.userAgent.indexOf("Safari") >= 0 && typeof r.status == "undefined"; } catch(e){} // 否则,返回false return false; }
检查超时
在XMLHttpRequest的默认实现中缺少判断请求超时的功能。下面是检查请求超时的一个例子:
// 建立异步请求 var xml = new XMLHttpRequest(); // 初始化异步post请求 xml.open("GET", "/some/url.cgi", true); // 超时时长为5秒 var timeoutLength = 5000; // 跟踪请求是否完成的变量 var requestDone = false; // 初始化一个5秒后执行的回调函数,用于取消请求 setTimeout(function(){ requestDone = true; }, timeoutLength); // 监听文档状态 xml.onreadystatechange = function(){ // 等待数据完全加载,并保证请求并未超时 if ( xml.readyState == 4 && !requestDone ) { // xml.responseXML 包含XML文档(如果返回的是XML) // xml.responseText 包含文本(如果返回的不是XML // 为避免内存泄漏,清理文档 xml = null; } }; // 与服务器建立连接 xml.send();
3. 处理响应数据
在服务器上可能返回3种数据格式,并用客户端来读取并操作它们:
□ XML:所有浏览器都提供了原生的XML文档处理支持,自动将它们转换为可用的DOM文档
□ HTML:和XML文档的区别在于,它通常以纯文本字符串的形式存放在一个HTML片段中
□ JSON:JavaScript Object Notation格式
这3总格式都各有其适合的场合,比如有时返回HTML要比返回XML更有意义。获取HTTP响应数据的重点是XMLHttpRequest对象的两个属性:
□ responseXML:如果服务器返回的是XML文档,这个属性包含预处理后DOM文档的引用。
□ responseText:包含从服务器返回的原始文本数据的引用。
从HTTP服务器响应中解析正确数据的一个函数:
// 从HTTP响应中解析数据的函数 // 有两个参数:一个XMLHttpRequest对象,和一个可选参数(从服务器返回的数据类型) // type的值包括:xml,script,text或html,默认是"" function httpData(r, type) { // 获取content-type头部信息 var ct = r.getResponseHeader("content-type"); // 若没有提供默认的类型,判断服务器返回的是否是XML类型 var data = !type && ct && ct.indexOf("xml") >= 0; // 如果为真则获得XML文档对象,否则返回文本内容 data = type == "xml" || data ? r.responseXML : r.responseText; // 如果指定类型为script,则以JavaScript形式返回文本 if ( type == "script" ) eval.call( window, data ); // 返回数据(或为XML文档,或为文本字符串) return data; }
4. 能够执行必要的Ajax相关任务的一个完整函数
// 执行Ajax请求的通用函数 // 包含一系列选项的参数 function ajax( options ) { // 如果没有提供选项值,则采用默认值 options = { // HTTP请求的类型 type: options.type || "POST", // 请求的url url: options.url || "", // 请求的超时时间 timeout: options.timeout || 5000, // 请求完成、失败或成功时执行的函数 onComplete: options.onComplete || function(){}, onError: options.onError || function(){}, onSuccess: options.onSuccess || function(){}, // 服务器返回的数据类型 data: options.data || "" }; // 建立异步请求 var xml = new XMLHttpRequest(); // 初始化异步post请求 xml.open("GET", "/some/url.cgi", true); // 超时时长为5秒 var timeoutLength = 5000; // 跟踪请求是否完成的变量 var requestDone = false; // 初始化一个5秒后执行的回调函数,用于取消请 setTimeout(function(){ requestDone = true; }, timeoutLength); // 监听文档状态 xml.onreadystatechange = function(){ // 等待数据完全加载,并保证请求并未超时 if ( xml.readyState == 4 && !requestDone ) { // 检查请求是否成功 if ( httpSuccess( xml ) ) { // 以服务器返回的数据作为参数调用onSuccess函数 options.onSuccess( httpData( xml, options.type ) ); // 否则就发生了错误,执行onError函数 } else { options.onError(); } // 调用onComplete回调函数 options.onComplete(); // 为避免内存泄漏,清理文档 xml = null; } }; // 与服务器建立连接 xml.send(); // 判断HTTP响应是否成功 function httpSuccess(r) { try { // 如果得不到服务器状态,且正在请求本地文件,则返回 true return !r.status && location.protocol == "file:" || // 200 至 300 之间的状态码,返回 true ( r.status >= 200 && r.status < 300 ) || // 文档为修改也返回 true r.status == 304 || // Safari在文档修改时返回空状态,返回true navigator.userAgent.indexOf("Safari") >= 0 && typeof r.status == "undefined"; } catch(e){} // 否则,返回false return false; } // 从HTTP相应中解析数据 function httpData(r,type) { // 获取content-type头部信息 var ct = r.getResponseHeader("content-type"); // 若没有提供默认的类型,判断服务器返回的是否是XML类型 var data = !type && ct && ct.indexOf("xml") >= 0; // 如果为真则获得XML文档对象,否则返回文本内容 data = type == "xml" || data ? r.responseXML : r.responseText; // 如果指定类型为script,则以JavaScript形式返回文本 if ( type == "script" ) eval.call( window, data ); // 返回数据(或为XML文档,或为文本字符串) return data; } }
转载请注明:陈童的博客 » Ajax — HTTP请求与响应