在過去幾年,JavaScript 已從讓人事後才想起的偶然對象變成最重要的 Web 語言。如果要指出一個推動這項技術顯著進步的因素,那就是基於 Ajax 的應用程序開發的出現。
開發關於該主題的技術
簡言之,Ajax 是一種開發技術,支持網站或應用程序,使用實時數據更新界面,無需頁面刷新。該功能創建了一種更為流暢且更具桌面風格的用戶體驗。
Ajax 簡史
Ajax 的發展歷史類似於其他許多一夜成名的技術。盡管 Ajax 似乎不知從何而來,但實際上,它已經存在很長一段時間了。多年的努力使其遍布 Web,在 Ajax 旗幟的帶領下創建工具和模式。縱觀最初網絡泡沫的 DHTML 時代,以及網絡公司破產後的黑暗年代,世界各地的開發人員解禁了 JavaScript 的超能力,將這個嶄新的、令人激動的應用程序模式引人 Web。
XMLHttpRequest
最早最重要的 Ajax 謎題是 XMLHttpRequest (XHR) API。XHR 是一種用於在 Web 浏覽器和 Web 服務器間傳輸數據消息的 JavaScript API。它支持浏覽器使用 HTTP POST(將數據傳到服務器)或 GET 請求(從後台服務器訪問數據)。該 API 是大多數 Ajax 交互的核心,也是現代 Web 開發的一項基本技術。
它也是 Microsoft® Internet Explorer® 團隊貢獻給 Internet 的最好禮物。
這是真的。早在 2000 年,XHR 最先出現於 IE 5 中。最初是由 Alex Hopmann 編寫的 Microsoft ® ActiveX® 控件,創建 XHR 是為了處理 Microsoft Outlook® Web Access,旨在解決高級(當時)前端接口和 Microsoft Exchange Server 間的交互。
盡管 Microsoft 的軟件包不完全算是 “出身貧賤”,但 XHR 的發展遠遠超出了最初產品的范圍,後來在各個主要浏覽器中得以實現,甚至作為一種 W3C 標准被采用。
先鋒
除了 Microsoft 之外,還有其他一些企業開始進軍原型 Ajax 領域。許多企業都開始嘗試使用這些技術,其中有兩個特別值得一提 — 一個是因為它是一個有趣且經常引用的 Ajax 開發腳注,另一個是因為它是真正將這些技術大眾化的 Internet 巨頭。
Oddpost
Oddpost 是 2002 年推出的基於 Web 的高級郵件客戶端。它利用許多目前人們所熟知的模式。在設計和交互方面,人們會想起桌面郵件客戶端。在系統內部,Oddpost 使用開發人員稱為 DataPacks 的概念將小塊數據從服務器傳輸到浏覽器。這將帶來一種全新體驗。
Oddpost 最後被 Yahoo!收購,成為 Yahoo! Mail 修訂版的基礎。
Google Maps、Google Suggest、Gmail 以及一篇重要文章
真正的變化開始於幾年後的 Gmail、Google Suggest 和 Google Maps 服務。這三項 Ajax 技術的使用使得 Web 開發界沸騰起來。它的響應能力和交互性對公眾而言是全新的。新的 Google 應用程序很快引起了轟動。
雖然了解它的人並不是很多,但 Web 開發界對此反響非常劇烈。當時,人們知道在 Web 應用程序開發中出現了一些新的、激動人心的內容。但在很長一段時期內,這個 “內容” 一度模糊不清。
人們需要的是一篇讓該內容明朗化的文章。
2005 年 2 月 18 日,Adaptive Path 的共同創立者兼總裁 Jesse James Garrett 撰寫了一篇題為 “Ajax: A New Approach to Web Applications” 的文章。在這篇文章中,他介紹了 Web 應用程序設計開發的趨勢,諸如 Gmail 和 Google Maps 這類應用程序人們一直都在使用。他稱這種趨勢為 “可能引發 Web 開發的根本性變革。”
他還為這種模式命名,這是一個重要的時刻,因為從這一刻起人們開始重點關注這一新趨勢,每個人(甚至是非專業人員)在談及 Web 開發界近期最新變化時都會提到它。在本文中,他是這樣介紹 Ajax 這種技術的:
定義 Ajax
Ajax 不是一種技術。實際上是幾種技術,每種技術都各有其特色,這些技術以全新強大方式融合在一起。Ajax 包含:
雖然這個技術說明從某種程度上講有些過時了,但基本模式依然是完整的:HTML 和 CSS 呈現數據和樣式,DOM 和相關方法支持頁面實時更新,XHR 支持與服務器通信,JavaScript 安排整體顯示。
本文的總體影響比較大。密集的大肆宣傳與亟待開發的創造力和能源相碰撞,掀起了一場革命,這實屬難得一見。由於 Ajax 被世界范圍的新一代創業企業所采用,它迅速走向 Web 開發范式的前沿。Ajax 從一個尋求市場策略的模糊趨勢一躍成為現代Web 設計的開發的關鍵組成部分。
庫
基於 Ajax 開發的一個關鍵驅動因素是幾個全功能 JavaScript 庫的演變和改進。除了經驗豐富的 JavaScript 開發人員,很少有人能夠真正理解 Ajax 底層技術。因此,即使在 DHTML 時代,雖然研究出了大部分浏覽器交互和動畫來應對瑣碎的超額,但數量有限的幾個經驗豐富的 JavaScript 工程師導致基於 Ajax 的站點需求和人才(他們可以從零開始編寫這樣一個界面)供應之間的差距的進一步擴大。通過提供隨時可用的交互和動畫,減少跨浏覽器差異和改進核心 JavaScript API 缺點的實現,Prototype、Dojo 和 jQuery 這類庫有助於大規模地填補這一空白。
異步 JavaScript 以及更多 JavaScript(對象表示法)
從原始 post 時代到現代,Ajax 領域的最大改變是引入了 JSON,JSON 是一種基於 JavaScript 的數據傳輸。提供更小的文件和更便利的原生 JavaScript 訪問(與 XML 使用的笨重的基於 DOM 的方法和屬性截然相反),JSON 很快就被開發人員用於進行數據傳輸。現在 JSON 已列入近期完成的 ECMAScript 規范的第 5 版。
JSON+Padding
原始 JSON 提議的一個顯著增強是 JSON+Padding (JSONP)。正如您所看到的,XMLHttpRequest 對象有一個嚴格的安全模型,只支持使用與請求頁面相同的域名和協議進行通信。JSONP 在這個跨域限制上創建了一種更為靈活的方法,將 JSON 響應包裝到一個用戶定義或系統提供的回調函數中。將 JSON 腳本添加到文檔之後,該方法將會提供即時數據訪問。該模式現在很常見,對於許多較大的 Web 服務,可以采用該實踐來支持混搭應用和其他內容聯合。
盡管 JSONP 非常流行,但它有一個明顯的便於惡意代碼入侵的漏洞。因為來自第三方的腳本標記注入允許所有內容在主機頁面上運行,所以,在數據提供者受到威脅時,或者主機頁面沒有留意插入頁面的資源時,惡意入侵潛能將會令人想象。
現在,您已經對 Ajax 歷史有所了解,接下來我們將開始探討將魔法變成現實的技術。盡管,一般的 JavaScript API 書籍在圖書館中隨處可見,但即使對於經驗豐富的開發人員,了解底層工作原理仍然是具有啟發意義的。
XMLHttpRequest API 和特性
盡管可以使用其他技術從服務器中返回數據,但是 XHR 仍然是大多數 Ajax 交互的核心。XHR 交互由兩部分組成:請求和響應。下面我們將逐個介紹。
安全模型
正如上面所提到的,原始 XMLHttpRequest 對象有一個嚴格的安全模型。這個同源策略只 允許使用與請求頁面相同的主機、協議和端口進行通信。這意味著不同域(example.com 和 example2.com)、不同主機(my.example.com 和 www.example.com)、不同協議(http://example.com 和 https://example.com)之間的通信是禁止的,這會產生錯誤消息。
隨著第二版 XHR 對象的開發,新的跨域請求協議工作將在 W3C 中完成,大量實現工作由浏覽器供應商完成,針對跨域請求的機制目前僅在 Internet Explorer 8+、Mozilla Firefox 3.5+、Apple Safari 4+ 以及 Google Chrome 中提供。盡管發展已經放緩,但仍在請求中發送了一個特定 “Origin” 報頭:
Origin: http://example.com
並將服務器配置為發送回一個匹配的 “Access-Control-Allow-Origin” 報頭:
Access-Control-Allow-Origin: :http://example.com
現在,可以使用 XHR 對象跨域進行雙向通信了。
請求
請求端有 4 種方法:
響應
響應也有幾個屬性和方法:
readyState
實例化完成後,XMLHttpRequest 對象有 5 種狀態,使用以下值表示:
一個通用 JavaScript 示例
在我們進一步介紹流行庫之前,先通過幾個原始的 JavaScript 示例來了解正在運用的核心技術。
樣例 HTML 文檔
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>Simple Ajax Example</title> <meta name="author" content="Rob Larsen"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="_assets/css/style.css" rel="external nofollow" > </head> <body> <div id="main"> <h1>Simple Ajax Example</h1> <p><strong id="activate">Click here</strong> and content will be appended after this paragraph</p> </div> <script src="_assets/js/ajax.js"></script> </body> </html>
下面舉例說明了一個簡單 GET 請求,該請求將處理 responseXML。這是該技術發展早期的典型 Ajax 交互。它可以在所有現代浏覽器以及 Internet Explorer 7 和 8 中運行。
一個基本 Ajax 函數
/* Here's a basic Ajax function */ var ajax = function( opts ) { /* We have an options argument. In addition, we want to have some smart defaults. */ opts = { //Is it a Get or Post type: opts.type || "POST", //What URL are we going to hit? url: opts.url || "", //What do we do with the data onSuccess: opts.onSuccess || function(){}, //what kind of data do we expect? data: opts.data || "xml" }; //create a new XMLHttpRequest var xhr = new XMLHttpRequest(); //Open the connection to the server xhr.open(opts.type, opts.url, true); /* When the ready state changes fire this function */ xhr.onreadystatechange = function(){ //readyState 4 is "done" if ( xhr.readyState == 4 ) { /* do some simple data processing There are two components to the returned object- responseXML and responseText. Depending on what we're doing we'll need one or the other. */ switch (opts.data){ case "json": //json is text opts.onSuccess(xhr.responseText); break; case "xml": //XML retains the structure/DOM //It's passed in whole. opts.onSuccess(xhr.responseXML); break; default : //Everything else will get TXT opts.onSuccess(xhr.responseText);; } } }; //close the connection xhr.send(null); } //here's our simple function var ajaxSample = function(e){ //Simple callback adds some text to the page var callback = function( data ) { document.getElementById("main").innerHTML += "<p>" +data.getElementsByTagName("data")[0].getAttribute("value") +"</p>"; } //And here's our Ajax call ajax({ type: "GET", url: "_assets/data/ajax-1.xml", onSuccess: callback, data : "xml" }) //prevent the default action e.preventDefault(); } //Wire everything up document.getElementById("activate").addEventListener("click", ajaxSample, false);
在下面的例子 中可以看到活動的原始 ActiveX 對象。如果沒有本機實現,可以在不同版本的 Internet Explorer 中使用 Try... Catch 塊來循環遍歷對象的潛在引用。這個完整的跨浏覽器實現與 Internet Explorer 是兼容的,甚至可以與古老的 Internet Explorer 5 兼容。
一個跨浏覽器 Ajax 腳本
var ajax = function( opts ) { opts = { type: opts.type || "POST", url: opts.url || "", onSuccess: opts.onSuccess || function(){}, data: opts.data || "xml" }; /* Support for the original ActiveX object in older versions of Internet Explorer This works all the way back to IE5. */ if ( typeof XMLHttpRequest == "undefined" ) { XMLHttpRequest = function () { try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e) {} try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (e) {} try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) {} throw new Error("No XMLHttpRequest."); }; } var xhr = new XMLHttpRequest(); xhr.open(opts.type, opts.url, true); xhr.onreadystatechange = function(){ if ( xhr.readyState == 4 ) { switch (opts.data){ case "json": opts.onSuccess(xhr.responseText); break; case "xml": opts.onSuccess(xhr.responseXML); break; default : opts.onSuccess(xhr.responseText);; } } }; xhr.send(null); } var ajaxSample = function(e){ var callback = function( data ) { document.getElementById("main").innerHTML += "<p>" +data.getElementsByTagName("data")[0].getAttribute("value") +"</p>"; } ajax({ type: "GET", url: "_assets/data/ajax-1.xml", onSuccess: callback, data: "xml" }) e.preventDefault(); } document.getElementById("activate").addEventListener("click", ajaxSample, false);
下面展示了現今更為常見的模式:采用 JSON 格式的 responseText,並將其解析成本機的 JavaScript 對象。這段代碼演示了一個較為簡單的 JSON 數據處理方法。為什麼眾多開發人員都選擇使用 JSON 來傳輸數據,將該清單與操作 XML 數據所需的偶爾間接且冗長的方法進行比較,答案顯而易見。
使用 JSON
var ajax = function( opts ) { opts = { type: opts.type || "POST", url: opts.url || "", onSuccess: opts.onSuccess || function(){}, data: opts.data || "xml" }; var xhr = new XMLHttpRequest(); xhr.open(opts.type, opts.url, true); xhr.onreadystatechange = function(){ if ( xhr.readyState == 4 ) { switch (opt.sdata){ case "json": opt.onSuccess(xhr.responseText); break; case "xml": opt.onSuccess(xhr.responseXML); break; default : opt.onSuccess(xhr.responseText);; } } }; xhr.send(null); } var jsonSample = function(e){ var callback = function( data ) { //here, the data is actually a string //we use JSON.parse to turn it into an object data = JSON.parse(data); /* we can then use regular JavaScript object references to get at our data. */ document.getElementById("main").innerHTML += "<p>" + data.sample.txt +"</p>"; } ajax({ type: "GET", url: "_assets/data/json-1.json", onSuccess: callback, data : "json" }) e.preventDefault(); } document.getElementById("activate").addEventListener("click", jsonSample, false);
下面例子都使用了 JSON 數據。
提供了一個簡單的 JSONP 示例。正如您所看到的,通過使用一個回調參數,可以避免將 XHR 完全地簡單附加到腳本中。返回給回調,並在可執行 JavaScript 代碼中包裝數據對象。
JSONP 數據
var callback = function( data ) { document.getElementById("main").innerHTML += "<p>"+ data.sample.txt +"</p>"; } var jsonpSample = function(e){ //create a script element var jsonp = document.createElement("script"); //give it a source with the callback name appended in the query string jsonp.src= "_assets/data/jsonp.do?callback=callback"; //add it to the doc document.body.appendChild(jsonp); e.preventDefault(); } //wire up the event document.getElementById("activate").addEventListener("click", jsonpSample, false);
庫示例
對於大多數開發人員來說,只有進行學術研究的人才會對 Ajax 請求的本質感興趣。大多數實際工作是在一個或多個 JavaScript 庫中完成。除了修補跨浏覽器不兼容性,這些庫都提供了構建於基礎 API 之上的特性。下列示例展示了 3 個流行庫中的 GET 和 POST 示例來介紹不同的 API。
jQuery
讓我們從流行 jQuery 庫開始舉例說明。jQuery 的 Ajax 函數最近進行了重寫,將幾個高級功能包含在內,這不是術語本文的討論范圍,但是所有 jQuery Ajax 請求的常見功能都以傳遞給該函數的配置對象的參數形式存在。另外還要注意的是,jQuery 有幾個便利的方法,比如 $.post 和$.get,這是常見請求配置的快捷鍵。
展示了使用 jQuery 獲取數據的簡要代碼。
一個 jQuery GET 請求
/* callback is a simple function that will be run when the data is returned from the server */ var callback = function( data ) { /* it just adds a little bit of text to the document data is the JSON object returned by the server. */ $("#main").append($("<p />").text(data.sample.txt)); } /* Wire up the ajax call to this click event */ $("#activate").click( function(){ //call $.ajax with a configuration object $.ajax({ //it's just a get request type: 'get', //we're looking for this URL url: '_assets/data/json-1.json', //Our cool callback function success: callback, //it's going to be JSON dataType: "json" }) } )
下面演示了如何發布和檢索簡單 JSON 對象。需要注意的是,這裡使用了原生 JSON 對象來分析輸入數據。jQuery 文檔明確提及需要通過 JSON2.js 腳本增加不受支持的浏覽器。
提供一個顯式錯誤句柄使得成功請求和失敗請求都能得到優雅的處理。jQuery 的錯誤狀態帶有 3 個參數,包括 XHR 對象本身,這支持健壯的錯誤處理。
一個 jQuery POST
/* this is the object we're going to post */ var myMessages = { positive : "Today is a good day", negative : "Today stinks", meh : "meh" } var callback = function( data ) { $("#main").append($("<p />").text(data.positive)); } /* Setting up a simple error handler. It doesn't do much. It's just nice to illustrate error handling. */ var errorHandler = function( xhr, textStatus, errorThrown ){ throw new Error("There was an error. The error status was " + textStatus ); } /* Here's where the action happens. Attach an event to out simple button. */ $("#activate").click( function(){ //call $.ajax with a configuration object $.ajax({ //we're sending data to the server type: 'POST', //this is our URL url: '_assets/data/post-responder.do', /* This is our data, JSON stringified jQuery expects to use native JSON or JSON2.js in unsupported browsers */ data: JSON.stringify(myMessages), //Here's where we set up our callback function success: callback, //The data expected from the server dataType: "json", //And our simple error handler error : errorHandler } ) } );
Dojo
Dojo 不僅僅是下列示例中演示的簡單 Ajax 請求/DOM 操作。它實際上是為硬核應用程序開發而構建的。這就是說,以這種方式查看 API 仍然是值得期待的。
注意兩個獨立的 “Ajax” 函數:xhrGet 和 xhrPost。另外還要注意的是,這裡使用了 Dojo JSON 實用函數來分析輸入數據。下面 展示了一個 GET 示例。
一個 Dojo GET 請求
var callback = function( data ) { //note the document.getelementById alias dojo.byId("main").innerHTML += "<p>"+ data.sample.txt +"</p>"; } var getData = function(){ //xhrGet is for get requests dojo.xhrGet({ //the URL of the request url: "_assets/data/json-1.json", //Handle the result as JSON data handleAs: "json", //The success handler load: callback }); } // Use connect to attach events dojo.connect( dojo.byId("activate"), "onclick", getData );
下面展示了一個 Dojo POST,包含一個錯誤句柄的配置。
Dojo POST
var myMessages = { positive : "Today is a good day", negative : "Today stinks", meh : "meh" } var callback = function( data ) { dojo.byId("main").innerHTML += "<p>"+ data.positive +"</p>"; } var errorHandler = function(){ throw new Error("We dun goofed.") } var postData = function(){ //not surprisingly xhrPost is for POST dojo.xhrPost({ // The URL of the request url: "_assets/data/post-responder.do", //This will be JSON handleAs: "json", //Set the headers properly headers: { "Content-Type": "application/json; charset=uft-8"}, //Use Dojo's JSON utility postData: dojo.toJson(myMessages), // The success handler load: callback, // The error handler error: errorHandler }); } // Use connect to attach events dojo.connect( dojo.byId("activate"), "onclick", postData );
Yahoo! 用戶界面 (YUI)
YUI 庫提供一個與前面兩個略有不同的模式。首先,YUI 返回整個 XHR 對象,不僅解析數據,還允許更准確地操作返回數據和整個請求的可見性。這也意味著開發人員需要了解 XHR 對象的來龍去脈。另外,這裡還展示了 YUI 模塊加載程序 use() 的使用,需要注意的是,即使與 Ajax 沒有直接聯系(除了加載 io 模塊之外)。中有一個 YUI 模塊列表,還有一個用作參數的回調函數。一旦運行,就可以從 Yahoo! Content Delivery Network (CDN) 下載數據包,Yahoo! Content Delivery Network (CDN) 包含單個基於 CDN 的下載包中所需的所有模塊。
一個 YUI GET 請求
// Create a new YUI instance and populate it with the required modules. YUI().use('node','event', 'json', 'io', function (Y) { var callback = function( id, xhr ) { var data = Y.JSON.parse(xhr.responseText); Y.one('#main').append("<p>" + data.sample.txt +"</p>"); } Y.one("#activate").on('click', function(){ Y.io( '_assets/data/json-1.json', { //This is actually the default method: 'get', on: {success: callback} }) } ) });
下面中的 POST 示例中呈現的一個有趣的樣式風格將所有響應函數進一步分割成 on 對象。
YUI POST
YUI().use('node','event', 'json', 'io', function (Y) { var myMessages = { positive : "Today is a good day", negative : "Today stinks", meh : "meh" } var callback = function( id, xhr ) { var data = Y.JSON.parse(xhr.responseText); Y.one('#main').append("<p>" + data.positive +"</p>"); } var errorHandler = function( id, xhr){ throw new Error("There was an error. The error status was " + xhr.statusText +".") } Y.one("#activate").on('click', function(){ Y.io( '_assets/data/post-responder.do', { method: 'post', //Use the Y.JSON utility to convert messages to a string data : Y.JSON.stringify(myMessages), //All response methods are encapsulated in //the on object on: {success: callback, failure: errorHandler } }) } ) });
正如您所看到的,基本模式在多數清單中都是一樣的。除了支持 ActiveX 控件和 JSONP 示例之外,它們基本上基於同一原理,只是在核心 JavaScript 交互的頂層具有不同的 API 變化。
請注意,除了這裡列出的基本交互之外,所有這些庫還提供大量特性。盡管您可以做的大多數 Ajax 工作可以通過簡單的 GET 和 POST 請求來處理,但讓自己熟悉所選擇的庫中的高級特性非常有用。