2345技术员联盟

Ajax 很全很完整教程

  • 来源:未知 原创
  • 时间:2018-07-23
  • 阅读:
  • 本文标签:

第 1 頁 Ajax 簡介


Ajax 由 HTML、JavaScript? 技術、DHTML 和 DOM 組成,這一傑出的方法可以將笨拙的 Web 界面轉化成交互性的 Ajax 應用程序。本文的作者是一位 Ajax 專家,他演示了這些技術如何協同工作 —— 從總體概述到細節的討論 —— 使高效的 Web 開發成為現實。他還揭開了 Ajax 核心概念的神秘面紗,包括 XMLHttpRequest 對象。


五年前,如果不知道 XML,您就是一只無人重視的醜小鴨。十八個月前,Ruby 成了關註的中心,不知道 Ruby 的程序員只能坐冷板凳了。今天,如果想跟上最新的技術時尚,那您的目標就是 Ajax。


但是,Ajax 不僅僅 是一種時尚,它是一種構建網站的強大方法,而且不像學習一種全新的語言那樣困難。


但在詳細探討 Ajax 是什麼之前,先讓我們花幾分鐘了解 Ajax 做 什麼。目前,編寫應用程序時有兩種基本的選擇:


·桌面應用程序 

·Web 應用程序


兩者是類似的,桌面應用程序通常以 CD 為介質(有時候可從網站下載)並完全安裝到您的計算機上。桌面應用程序可能使用互聯網下載更新,但運行這些應用程序的代碼在桌面計算機上。Web 應用程序運行在某處的 Web 服務器上 —— 毫不奇怪,要通過 Web 瀏覽器訪問這種應用程序。


不過,比這些應用程序的運行代碼放在何處更重要的是,應用程序如何運轉以及如何與其進行交互。桌面應用程序一般很快(就在您的計算機上運行,不用等待互聯網連接),具有漂亮的用戶界面(通常和操作系統有關)和非凡的動態性。可以單擊、選擇、輸入、打開菜單和子菜單、到處巡遊,基本上不需要等待。


另一方面,Web 應用程序是最新的潮流,它們提供了在桌面上不能實現的服務(比如 Amazon.com 和 eBay)。但是,伴隨著 Web 的強大而出現的是等待,等待服務器響應,等待屏幕刷新,等待請求返回和生成新的頁面。


顯然這樣說過於簡略了,但基本的概念就是如此。您可能已經猜到,Ajax 嘗試建立桌面應用程序的功能和交互性,與不斷更新的 Web 應用程序之間的橋梁。可以使用像桌面應用程序中常見的動態用戶界面和漂亮的控件,不過是在 Web 應用程序中。


還等什麼呢?我們來看看 Ajax 如何將笨拙的 Web 界面轉化成能迅速響應的 Ajax 應用程序吧。


老技術,新技巧


在談到 Ajax 時,實際上涉及到多種技術,要靈活地運用它必須深入了解這些不同的技術(本系列的頭幾篇文章將分別討論這些技術)。好消息是您可能已經非常熟悉其中的大部分技術,更好的是這些技術都很容易學習,並不像完整的編程語言(如 Java 或 Ruby)那樣困難。


下面是 Ajax 應用程序所用到的基本技術:


·HTML 用於建立 Web 表單並確定應用程序其他部分使用的字段。 

·JavaScript 代碼是運行 Ajax 應用程序的核心代碼,幫助改進與服務器應用程序的通信。 

·DHTML 或 Dynamic HTML,用於動態更新表單。我們將使用 div、span 和其他動態 HTML 元素來標記 HTML。 

·文檔對象模型 DOM 用於(通過 JavaScript 代碼)處理 HTML 結構和(某些情況下)服務器返回的 XML。


Ajax 的定義


順便說一下,Ajax 是 Asynchronous JavaScript and XML(以及 DHTML 等)的縮寫。這個短語是 Adaptive Path 的 Jesse James Garrett 發明的(請參閱 參考資料),按照 Jesse 的解釋,這不是 個首字母縮寫詞。


我們來進一步分析這些技術的職責。以後的文章中我將深入討論這些技術,目前只要熟悉這些組件和技術就可以了。對這些代碼越熟悉,就越容易從對這些技術的零散了解轉變到真正把握這些技術(同時也真正打開了 Web 應用程序開發的大門)。


XMLHttpRequest 對象


要了解的一個對象可能對您來說也是最陌生的,即 XMLHttpRequest。這是一個 JavaScript 對象,創建該對象很簡單,如清單 1 所示。


清單 1. 創建新的 XMLHttpRequest 對象


<script language="javascript" type="text/javascript">

var xmlHttp = new XMLHttpRequest();

</script>

下一期文章中將進一步討論這個對象,現在要知道這是處理所有服務器通信的對象。繼續閱讀之前,先停下來想一想:通過 XMLHttpRequest 對象與服務器進行對話的是 JavaScript 技術。這不是一般的應用程序流,這恰恰是 Ajax 的強大功能的來源。


在一般的 Web 應用程序中,用戶填寫表單字段並單擊 Submit 按鈕。然後整個表單發送到服務器,服務器將它轉發給處理表單的腳本(通常是 PHP 或 Java,也可能是 CGI 進程或者類似的東西),腳本執行完成後再發送回全新的頁面。該頁面可能是帶有已經填充某些數據的新表單的 HTML,也可能是確認頁面,或者是具有根據原來表單中輸入數據選擇的某些選項的頁面。當然,在服務器上的腳本或程序處理和返回新表單時用戶必須等待。屏幕變成一片空白,等到服務器返回數據後再重新繪制。這就是交互性差的原因,用戶得不到立即反饋,因此感覺不同於桌面應用程序。


Ajax 基本上就是把 JavaScript 技術和 XMLHttpRequest 對象放在 Web 表單和服務器之間。當用戶填寫表單時,數據發送給一些 JavaScript 代碼而不是 直接發送給服務器。相反,JavaScript 代碼捕獲表單數據並向服務器發送請求。同時用戶屏幕上的表單也不會閃爍、消失或延遲。換句話說,JavaScript 代碼在幕後發送請求,用戶甚至不知道請求的發出。更好的是,請求是異步發送的,就是說 JavaScript 代碼(和用戶)不用等待服務器的響應。因此用戶可以繼續輸入數據、滾動屏幕和使用應用程序。


然後,服務器將數據返回 JavaScript 代碼(仍然在 Web 表單中),後者決定如何處理這些數據。它可以迅速更新表單數據,讓人感覺應用程序是立即完成的,表單沒有提交或刷新而用戶得到了新數據。JavaScript 代碼甚至可以對收到的數據執行某種計算,再發送另一個請求,完全不需要用戶幹預!這就是 XMLHttpRequest 的強大之處。它可以根據需要自行與服務器進行交互,用戶甚至可以完全不知道幕後發生的一切。結果就是類似於桌面應用程序的動態、快速響應、高交互性的體驗,但是背後又擁有互聯網的全部強大力量。


加入一些 JavaScript


得到 XMLHttpRequest 的句柄後,其他的 JavaScript 代碼就非常簡單了。事實上,我們將使用 JavaScript 代碼完成非常基本的任務:


·獲取表單數據:JavaScript 代碼很容易從 HTML 表單中抽取數據並發送到服務器。 

·修改表單上的數據:更新表單也很簡單,從設置字段值到迅速替換圖像。 

·解析 HTML 和 XML:使用 JavaScript 代碼操縱 DOM(請參閱 下一節),處理 HTML 表單服務器返回的 XML 數據的結構。 


對於前兩點,需要非常熟悉 getElementById() 方法,如 清單 2 所示。


清單 2. 用 JavaScript 代碼捕獲和設置字段值



// Get the value of the "phone" field and stuff it in a variable called phone

var phone = document.getElementById("phone").value;


// Set some values on a form using an array called response

document.getElementById("order").value = response[0];

document.getElementById("address").value = response[1];

這裏沒有特別需要註意的地方,真是好極了!您應該認識到這裏並沒有非常復雜的東西。只要掌握了 XMLHttpRequest,Ajax 應用程序的其他部分就是如 清單 2 所示的簡單 JavaScript 代碼了,混合有少量的 HTML。同時,還要用一點兒 DOM,我們就來看看吧。


以 DOM 結束


最後還有 DOM,即文檔對象模型。可能對有些讀者來說 DOM 有點兒令人生畏,HTML 設計者很少使用它,即使 JavaScript 程序員也不大用到它,除非要完成某項高端編程任務。大量使用 DOM 的是 復雜的 Java 和 C/C++ 程序,這可能就是 DOM 被認為難以學習的原因。


幸運的是,在 JavaScript 技術中使用 DOM 很容易,也非常直觀。現在,按照常規也許應該說明如何使用 DOM,或者至少要給出一些示例代碼,但這樣做也可能誤導您。即使不理會 DOM,仍然能深入地探討 Ajax,這也是我準備采用的方法。以後的文章將再次討論 DOM,現在只要知道可能需要 DOM 就可以了。當需要在 JavaScript 代碼和服務器之間傳遞 XML 和改變 HTML 表單的時候,我們再深入研究 DOM。沒有它也能做一些有趣的工作,因此現在就把 DOM 放到一邊吧。


獲取 Request 對象


有了上面的基礎知識後,我們來看看一些具體的例子。XMLHttpRequest 是 Ajax 應用程序的核心,而且對很多讀者來說可能還比較陌生,我們就從這裏開始吧。從 清單 1 可以看出,創建和使用這個對象非常簡單,不是嗎?等一等。


還記得幾年前的那些討厭的瀏覽器戰爭嗎?沒有一樣東西在不同的瀏覽器上得到同樣的結果。不管您是否相信,這些戰爭仍然在繼續,雖然規模較小。但令人奇怪的是,XMLHttpRequest 成了這場戰爭的犧牲品之一。因此獲得 XMLHttpRequest 對象可能需要采用不同的方法。下面我將詳細地進行解釋。


使用 Microsoft 瀏覽器


Microsoft 瀏覽器 Internet Explorer 使用 MSXML 解析器處理 XML(可以通過 參考資料 進一步了解 MSXML)。因此如果編寫的 Ajax 應用程序要和 Internet Explorer 打交道,那麼必須用一種特殊的方式創建對象。


但並不是這麼簡單。根據 Internet Explorer 中安裝的 JavaScript 技術版本不同,MSXML 實際上有兩種不同的版本,因此必須對這兩種情況分別編寫代碼。請參閱 清單 3,其中的代碼在 Microsoft 瀏覽器上創建了一個 XMLHttpRequest。


清單 3. 在 Microsoft 瀏覽器上創建 XMLHttpRequest 對象


var xmlHttp = false;

try {

  xmlHttp = new ActiveXObject("Msxml2.XMLHTTP");

} catch (e) {

  try {

    xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");

  } catch (e2) {

    xmlHttp = false;

  }

}

您對這些代碼可能還不完全理解,但沒有關系。當本系列文章結束的時候,您將對 JavaScript 編程、錯誤處理、條件編譯等有更深的了解。現在只要牢牢記住其中的兩行代碼:


xmlHttp = new ActiveXObject("Msxml2.XMLHTTP");



xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");。


這兩行代碼基本上就是嘗試使用一個版本的 MSXML 創建對象,如果失敗則使用另一個版本創建該對象。不錯吧?如果都不成功,則將 xmlHttp 變量設為 false,告訴您的代碼出現了問題。如果出現這種情況,可能是因為安裝了非 Microsoft 瀏覽器,需要使用不同的代碼。


處理 Mozilla 和非 Microsoft 瀏覽器


如果選擇的瀏覽器不是 Internet Explorer,或者為非 Microsoft 瀏覽器編寫代碼,就需要使用不同的代碼。事實上就是 清單 1 所示的一行簡單代碼:


var xmlHttp = new XMLHttpRequest object;。


這行簡單得多的代碼在 Mozilla、Firefox、Safari、Opera 以及基本上所有以任何形式或方式支持 Ajax 的非 Microsoft 瀏覽器中,創建了 XMLHttpRequest 對象。


結合起來


關鍵是要支持所有 瀏覽器。誰願意編寫一個只能用於 Internet Explorer 或者非 Microsoft 瀏覽器的應用程序呢?或者更糟,要編寫一個應用程序兩次?當然不!因此代碼要同時支持 Internet Explorer 和非 Microsoft 瀏覽器。清單 4 顯示了這樣的代碼。


清單 4. 以支持多種瀏覽器的方式創建 XMLHttpRequest 對象



/* Create a new XMLHttpRequest object to talk to the Web server */

var xmlHttp = false;

/*@cc_on @*/

/*@if (@_jscript_version >= 5)

try {

  xmlHttp = new ActiveXObject("Msxml2.XMLHTTP");

} catch (e) {

  try {

    xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");

  } catch (e2) {

    xmlHttp = false;

  }

}

@end @*/


if (!xmlHttp &amp;& typeof XMLHttpRequest != 'undefined') {

  xmlHttp = new XMLHttpRequest();

}

現在先不管那些註釋掉的奇怪符號,如 @cc_on,這是特殊的 JavaScript 編譯器命令,將在下一期針對 XMLHttpRequest 的文章中詳細討論。這段代碼的核心分為三步:

1、建立一個變量 xmlHttp 來引用即將創建的 XMLHttpRequest 對象。 

2、嘗試在 Microsoft 瀏覽器中創建該對象: 

      1)嘗試使用 Msxml2.XMLHTTP 對象創建它。 

      2)如果失敗,再嘗試 Microsoft.XMLHTTP 對象。 

2、如果仍然沒有建立 xmlHttp,則以非 Microsoft 的方式創建該對象。 

最後,xmlHttp 應該引用一個有效的 XMLHttpRequest 對象,無論運行什麼樣的瀏覽器。


關於安全性的一點說明


安全性如何呢?現在瀏覽器允許用戶提高他們的安全等級,關閉 JavaScript 技術,禁用瀏覽器中的任何選項。在這種情況下,代碼無論如何都不會工作。此時必須適當地處理問題,這需要單獨的一篇文章來討論,要放到以後了(這個系列夠長了吧?不用擔心,讀完之前也許您就掌握了)。現在要編寫一段健壯但不夠完美的代碼,對於掌握 Ajax 來說就很好了。以後我們還將討論更多的細節。


Ajax 世界中的請求/響應


現在我們介紹了 Ajax,對 XMLHttpRequest 對象以及如何創建它也有了基本的了解。如果閱讀得很仔細,您可能已經知道與服務器上的 Web 應用程序打交道的是 JavaScript 技術,而不是直接提交給那個應用程序的 HTML 表單。


還缺少什麼呢?到底如何使用 XMLHttpRequest。因為這段代碼非常重要,您編寫的每個 Ajax 應用程序都要以某種形式使用它,先看看 Ajax 的基本請求/響應模型是什麼樣吧。


發出請求


您已經有了一個嶄新的 XMLHttpRequest 對象,現在讓它幹點活兒吧。首先需要一個 Web 頁面能夠調用的 JavaScript 方法(比如當用戶輸入文本或者從菜單中選擇一項時)。接下來就是在所有 Ajax 應用程序中基本都雷同的流程:


1、從 Web 表單中獲取需要的數據。 

2、建立要連接的 URL。 

3、打開到服務器的連接。 

4、設置服務器在完成後要運行的函數。 

5、發送請求。 


清單 5 中的示例 Ajax 方法就是按照這個順序組織的:


清單 5. 發出 Ajax 請求



function callServer() {

  // Get the city and state from the web form

  var city = document.getElementById("city").value;

  var state = document.getElementById("state").value;

  // Only go on if there are values for both fields

  if ((city == null) || (city == "")) return;

  if ((state == null) || (state == "")) return;


  // Build the URL to connect to

  var url = "/scripts/getZipCode.php?city=" + escape(city) + "&state=" + escape(state);


  // Open a connection to the server

  xmlHttp.open("GET", url, true);


  // Setup a function for the server to run when it's done

  xmlHttp.onreadystatechange = updatePage;


  // Send the request

  xmlHttp.send(null);

}

其中大部分代碼意義都很明確。開始的代碼使用基本 JavaScript 代碼獲取幾個表單字段的值。然後設置一個 PHP 腳本作為鏈接的目標。要註意腳本 URL 的指定方式,city 和 state(來自表單)使用簡單的 GET 參數附加在 URL 之後。


然後打開一個連接,這是您第一次看到使用 XMLHttpRequest。其中指定了連接方法(GET)和要連接的 URL。最後一個參數如果設為 true,那麼將請求一個異步連接(這就是 Ajax 的由來)。如果使用 false,那麼代碼發出請求後將等待服務器返回的響應。如果設為 true,當服務器在後臺處理請求的時候用戶仍然可以使用表單(甚至調用其他 JavaScript 方法)。


xmlHttp(要記住,這是 XMLHttpRequest 對象實例)的 onreadystatechange 屬性可以告訴服務器在運行完成 後(可能要用五分鐘或者五個小時)做什麼。因為代碼沒有等待服務器,必須讓服務器知道怎麼做以便您能作出響應。在這個示例中,如果服務器處理完了請求,一個特殊的名為 updatePage() 的方法將被觸發。


最後,使用值 null 調用 send()。因為已經在請求 URL 中添加了要發送給服務器的數據(city 和 state),所以請求中不需要發送任何數據。這樣就發出了請求,服務器按照您的要求工作。


如果沒有發現任何新鮮的東西,您應該體會到這是多麼簡單明了!除了牢牢記住 Ajax 的異步特性外,這些內容都相當簡單。應該感激 Ajax 使您能夠專心編寫漂亮的應用程序和界面,而不用擔心復雜的 HTTP 請求/響應代碼。


清單 5 中的代碼說明了 Ajax 的易用性。數據是簡單的文本,可以作為請求 URL 的一部分。用 GET 而不是更復雜的 POST 發送請求。沒有 XML 和要添加的內容頭部,請求體中沒有要發送的數據;換句話說,這就是 Ajax 的烏托邦。


不用擔心,隨著本系列文章的展開,事情會變得越來越復雜。您將看到如何發送 POST 請求、如何設置請求頭部和內容類型、如何在消息中編碼 XML、如何增加請求的安全性,可以做的工作還有很多!暫時先不用管那些難點,掌握好基本的東西就行了,很快我們就會建立一整套的 Ajax 工具庫。


處理響應


現在要面對服務器的響應了。現在只要知道兩點:


·什麼也不要做,直到 xmlHttp.readyState 屬性的值等於 4。 

·服務器將把響應填充到 xmlHttp.responseText 屬性中。 


其中的第一點,即就緒狀態,將在下一篇文章中詳細討論,您將進一步了解 HTTP 請求的階段,可能比您設想的還多。現在只要檢查一個特定的值(4)就可以了(下一期文章中還有更多的值要介紹)。第二點,使用 xmlHttp.responseText 屬性獲得服務器的響應,這很簡單。清單 6 中的示例方法可供服務器根據 清單 5 中發送的數據調用。


清單 6. 處理服務器響應


function updatePage() {

  if (xmlHttp.readyState == 4) {

    var response = xmlHttp.responseText;

    document.getElementById("zipCode").value = response;

  }

}

這些代碼同樣既不難也不復雜。它等待服務器調用,如果是就緒狀態,則使用服務器返回的值(這裏是用戶輸入的城市和州的 ZIP 編碼)設置另一個表單字段的值。於是包含 ZIP 編碼的 zipCode 字段突然出現了,而用戶沒有按任何按鈕!這就是前面所說的桌面應用程序的感覺。快速響應、動態感受等等,這些都只因為有了小小的一段 Ajax 代碼。


細心的讀者可能註意到 zipCode 是一個普通的文本字段。一旦服務器返回 ZIP 編碼,updatePage() 方法就用城市/州的 ZIP 編碼設置那個字段的值,用戶就可以改寫該值。這樣做有兩個原因:保持例子簡單,說明有時候可能希望 用戶能夠修改服務器返回的數據。要記住這兩點,它們對於好的用戶界面設計來說很重要。


連接 Web 表單


還有什麼呢?實際上沒有多少了。一個 JavaScript 方法捕捉用戶輸入表單的信息並將其發送到服務器,另一個 JavaScript 方法監聽和處理響應,並在響應返回時設置字段的值。所有這些實際上都依賴於調用 第一個 JavaScript 方法,它啟動了整個過程。最明顯的辦法是在 HTML 表單中增加一個按鈕,但這是 2001 年的辦法,您不這樣認為嗎?還是像 清單 7 這樣利用 JavaScript 技術吧。


清單 7. 啟動一個 Ajax 過程


<form>

<p>City: <input type="text" name="city" id="city" size="25" 

       onChange="callServer();" /></p>

<p>State: <input type="text" name="state" id="state" size="25" 

       onChange="callServer();" /></p>

<p>Zip Code: <input type="text" name="zipCode" id="city" size="5" /></p>

</form>

如果感覺這像是一段相當普通的代碼,那就對了,正是如此!當用戶在 city 或 state 字段中輸入新的值時,callServer() 方法就被觸發,於是 Ajax 開始運行了。有點兒明白怎麼回事了吧?好,就是如此!


結束語


現在您可能已經準備開始編寫第一個 Ajax 應用程序了,至少也希望認真讀一下 參考資料 中的那些文章了吧?但可以首先從這些應用程序如何工作的基本概念開始,對 XMLHttpRequest 對象有基本的了解。在下一期文章中,您將掌握這個對象,學會如何處理 JavaScript 和服務器的通信、如何使用 HTML 表單以及如何獲得 DOM 句柄。


現在先花點兒時間考慮考慮 Ajax 應用程序有多麼強大。設想一下,當單擊按鈕、輸入一個字段、從組合框中選擇一個選項或者用鼠標在屏幕上拖動時,Web 表單能夠立刻作出響應會是什麼情形。想一想異步 究竟意味著什麼,想一想 JavaScript 代碼運行而且不等待 服務器對它的請求作出響應。會遇到什麼樣的問題?會進入什麼樣的領域?考慮到這種新的方法,編程的時候應如何改變表單的設計?


如果在這些問題上花一點兒時間,與簡單地剪切/粘貼某些代碼到您根本不理解的應用程序中相比,收益會更多。在下一期文章中,我們將把這些概念付諸實踐,詳細介紹使應用程序按照這種方式工作所需要的代碼。因此,現在先享受一下 Ajax 所帶來的可能性吧。


第 2 頁 使用 JavaScript 和 Ajax 發出異步請求


多數 Web 應用程序都使用請求/響應模型從服務器上獲得完整的 HTML 頁面。常常是點擊一個按鈕,等待服務器響應,再點擊另一個按鈕,然後再等待,這樣一個反復的過程。有了 Ajax 和 XMLHttpRequest 對象,就可以使用不必讓用戶等待服務器響應的請求/響應模型了。本文中,Brett McLaughlin 介紹了如何創建能夠適應不同瀏覽器的 XMLHttpRequest 實例,建立和發送請求,並響應服務器。


本系列的上一期文章(請參閱 參考資料 中的鏈接),我們介紹了 Ajax 應用程序,考察了推動 Ajax 應用程序的基本概念。其中的核心是很多您可能已經了解的技術:JavaScript、HTML 和 XHTML、一點動態 HTML 以及 DOM(文檔對象模型)。本文將放大其中的一點,把目光放到具體的 Ajax 細節上。


本文中,您將開始接觸最基本和基礎性的有關 Ajax 的全部對象和編程方法:XMLHttpRequest 對象。該對象實際上僅僅是一個跨越所有 Ajax 應用程序的公共線程,您可能已經預料到,只有徹底理解該對象才能充分發揮編程的潛力。事實上,有時您會發現,要正確地使用 XMLHttpRequest,顯然不能 使用 XMLHttpRequest。這到底是怎麼回事呢?


Web 2.0 一瞥


在深入研究代碼之前首先看看最近的觀點 —— 一定要十分清楚 Web 2.0 這個概念。聽到 Web 2.0 這個詞的時候,應該首先問一問 “Web 1.0 是什麼?” 雖然很少聽人提到 Web 1.0,實際上它指的就是具有完全不同的請求和響應模型的傳統 Web。比如,到 Amazon.com 網站上點擊一個按鈕或者輸入搜索項。就會對服務器發送一個請求,然後響應再返回到瀏覽器。該請求不僅僅是圖書和書目列表,而是另一個完整的 HTML 頁面。因此當 Web 瀏覽器用新的 HTML 頁面重繪時,可能會看到閃爍或抖動。事實上,通過看到的每個新頁面可以清晰地看到請求和響應。


Web 2.0(在很大程度上)消除了這種看得見的往復交互。比如訪問 Google Maps 或 Flickr 這樣的站點(到這些支持 Web 2.0 和 Ajax 站點的鏈接請參閱 參考資料)。比如在 Google Maps 上,您可以拖動地圖,放大和縮小,只有很少的重繪操作。當然這裏仍然有請求和響應,只不過都藏到了幕後。作為用戶,體驗更加舒適,感覺很像桌面應用程序。這種新的感受和範型就是當有人提到 Web 2.0 時您所體會到的。


需要關心的是如何使這些新的交互成為可能。顯然,仍然需要發出請求和接收響應,但正是針對每次請求/響應交互的 HTML 重繪造成了緩慢、笨拙的 Web 交互的感受。因此很清楚,我們需要一種方法使發送的請求和接收的響應只 包含需要的數據而不是整個 HTML 頁面。惟一需要獲得整個新 HTML 頁面的時候就是希望用戶看到 新頁面的時候。


但多數交互都是在已有頁面上增加細節、修改主體文本或者覆蓋原有數據。這些情況下,Ajax 和 Web 2.0 方法允許在不 更新整個 HTML 頁面的情況下發送和接收數據。對於那些經常上網的人,這種能力可以讓您的應用程序感覺更快、響應更及時,讓他們不時地光顧您的網站。


XMLHttpRequest 簡介


要真正實現這種絢麗的奇跡,必須非常熟悉一個 JavaScript 對象,即 XMLHttpRequest。這個小小的對象實際上已經在幾種瀏覽器中存在一段時間了,它是本專欄今後幾個月中要介紹的 Web 2.0、Ajax 和大部分其他內容的核心。為了讓您快速地大體了解它,下面給出將要用於該對象的很少的幾個 方法和屬性。


·open():建立到服務器的新請求。 

·send():向服務器發送請求。 

·abort():退出當前請求。 

·readyState:提供當前 HTML 的就緒狀態。 

·responseText:服務器返回的請求響應文本。 


如果不了解這些(或者其中的任何 一個),您也不用擔心,後面幾篇文章中我們將介紹每個方法和屬性。現在應該 了解的是,明確用 XMLHttpRequest 做什麼。要註意這些方法和屬性都與發送請求及處理響應有關。事實上,如果看到 XMLHttpRequest 的所有方法和屬性,就會發現它們都 與非常簡單的請求/響應模型有關。顯然,我們不會遇到特別新的 GUI 對象或者創建用戶交互的某種超極神秘的方法,我們將使用非常簡單的請求和非常簡單的響應。聽起來似乎沒有多少吸引力,但是用好該對象可以徹底改變您的應用程序。


簡單的 new


首先需要創建一個新變量並賦給它一個 XMLHttpRequest 對象實例。這在 JavaScript 中很簡單,只要對該對象名使用 new 關鍵字即可,如 清單 1 所示。


清單 1. 創建新的 XMLHttpRequest 對象


<script language="javascript" type="text/javascript">

var request = new XMLHttpRequest();

</script>

不難吧?記住,JavaScript 不要求指定變量類型,因此不需要像 清單 2 那樣做(在 Java 語言中可能需要這樣)。


清單 2. 創建 XMLHttpRequest 的 Java 偽代碼


XMLHttpRequest request = new XMLHttpRequest();

因此在 JavaScript 中用 var 創建一個變量,給它一個名字(如 “request”),然後賦給它一個新的 XMLHttpRequest 實例。此後就可以在函數中使用該對象了。


錯誤處理


在實際上各種事情都可能出錯,而上面的代碼沒有提供任何錯誤處理。較好的辦法是創建該對象,並在出現問題時優雅地退出。比如,任何較早的瀏覽器(不論您是否相信,仍然有人在使用老版本的 Netscape Navigator)都不支持 XMLHttpRequest,您需要讓這些用戶知道有些地方出了問題。清單 3 說明如何創建該對象,以便在出現問題的時候發出 JavaScript 警告。


清單 3. 創建具有錯誤處理能力的 XMLHttpRequest



<script language="javascript" type="text/javascript">

var request = false;

try {

  request = new XMLHttpRequest();

} catch (failed) {

  request = false;

}


if (!request)

  alert("Error initializing XMLHttpRequest!");

</script>

一定要理解這些步驟:


1、創建一個新變量 request 並賦值 false。後面將使用 false 作為判定條件,它表示還沒有創建 XMLHttpRequest 對象。 

2、增加 try/catch 塊: 

     1)嘗試創建 XMLHttpRequest 對象。 

     2)如果失敗(catch (failed))則保證 request 的值仍然為 false。 

3、檢查 request 是否仍為 false(如果一切正常就不會是 false)。 

4、如果出現問題(request 是 false)則使用 JavaScript 警告通知用戶出現了問題。 


代碼非常簡單,對大多數 JavaScript 和 Web 開發人員來說,真正理解它要比讀寫代碼花更長的時間。現在已經得到了一段帶有錯誤檢查的 XMLHttpRequest 對象創建代碼,還可以告訴您哪兒出了問題。


應付 Microsoft


看起來似乎一切良好,至少在用 Internet Explorer 試驗這些代碼之前是這樣的。如果這樣試驗的話,就會看到 圖 1 所示的糟糕情形。


圖 1. Internet Explorer 報告錯誤




顯然有什麼地方不對勁,而 Internet Explorer 很難說是一種過時的瀏覽器,因為全世界有 70% 在使用 Internet Explorer。換句話說,如果不支持 Microsoft 和 Internet Explorer 就不會受到 Web 世界的歡迎!因此我們需要采用不同的方法處理 Microsoft 瀏覽器。


經驗證發現 Microsoft 支持 Ajax,但是其 XMLHttpRequest 版本有不同的稱呼。事實上,它將其稱為幾種 不同的東西。如果使用較新版本的 Internet Explorer,則需要使用對象 Msxml2.XMLHTTP,而較老版本的 Internet Explorer 則使用 Microsoft.XMLHTTP。我們需要支持這兩種對象類型(同時還要支持非 Microsoft 瀏覽器)。請看看 清單 4,它在前述代碼的基礎上增加了對 Microsoft 的支持。


Microsoft 參與了嗎?


關於 Ajax 和 Microsoft 對該領域不斷增長的興趣和參與已經有很多文章進行了介紹。事實上,據說 Microsoft 最新版本的 Internet Explorer —— version 7.0,將在 2006 年下半年推出 —— 將開始直接支持 XMLHttpRequest,讓您使用 new 關鍵字代替所有的 Msxml2.XMLHTTP 創建代碼。但不要太激動,仍然需要支持舊的瀏覽器,因此跨瀏覽器代碼不會很快消失。


清單 4. 增加對 Microsoft 瀏覽器的支持



<script language="javascript" type="text/javascript">

var request = false;

try {

  request = new XMLHttpRequest();

} catch (trymicrosoft) {

  try {

    request = new ActiveXObject("Msxml2.XMLHTTP");

  } catch (othermicrosoft) {

    try {

      request = new ActiveXObject("Microsoft.XMLHTTP");

    } catch (failed) {

      request = false;

    }

  }

}


if (!request)

  alert("Error initializing XMLHttpRequest!");

</script>


很容易被這些花括號迷住了眼睛,因此下面分別介紹每一步:


1、創建一個新變量 request 並賦值 false。使用 false 作為判斷條件,它表示還沒有創建 XMLHttpRequest 對象。 

2、增加 try/catch 塊: 

    1)嘗試創建 XMLHttpRequest 對象。 

    2)如果失敗(catch (trymicrosoft)): 

            1>嘗試使用較新版本的 Microsoft 瀏覽器創建 Microsoft 兼容的對象(Msxml2.XMLHTTP)。 

            2> 如果失敗(catch (othermicrosoft))嘗試使用較老版本的 Microsoft 瀏覽器創建 Microsoft 兼容的對象(Microsoft.XMLHTTP)。 

    2)如果失敗(catch (failed))則保證 request 的值仍然為 false。 

3、檢查 request 是否仍然為 false(如果一切順利就不會是 false)。 

4、如果出現問題(request 是 false)則使用 JavaScript 警告通知用戶出現了問題。 


這樣修改代碼之後再使用 Internet Explorer 試驗,就應該看到已經創建的表單(沒有錯誤消息)。我實驗的結果如 圖 2 所示。


圖 2. Internet Explorer 正常工作




靜態與動態


再看一看清單 1、3 和 4,註意,所有這些代碼都直接嵌套在 script 標記中。像這種不放到方法或函數體中的 JavaScript 代碼稱為靜態 JavaScript。就是說代碼是在頁面顯示給用戶之前的某個時候運行。(雖然根據規範不能完全精確地 知道這些代碼何時運行對瀏覽器有什麼影響,但是可以保證這些代碼在用戶能夠與頁面交互之前運行。)這也是多數 Ajax 程序員創建 XMLHttpRequest 對象的一般方式。


就是說,也可以像 清單 5 那樣將這些代碼放在一個方法中。


清單 5. 將 XMLHttpRequest 創建代碼移動到方法中



<script language="javascript" type="text/javascript">


var request;


function createRequest() {

  try {

    request = new XMLHttpRequest();

  } catch (trymicrosoft) {

    try {

      request = new ActiveXObject("Msxml2.XMLHTTP");

    } catch (othermicrosoft) {

      try {

        request = new ActiveXObject("Microsoft.XMLHTTP");

      } catch (failed) {

        request = false;

      }

    }

  }


  if (!request)

    alert("Error initializing XMLHttpRequest!");

}

</script>


如果按照這種方式編寫代碼,那麼在處理 Ajax 之前需要調用該方法。因此還需要 清單 6 這樣的代碼。


清單 6. 使用 XMLHttpRequest 的創建方法



<script language="javascript" type="text/javascript">


var request;


function createRequest() {

  try {

    request = new XMLHttpRequest();

  } catch (trymicrosoft) {

    try {

      request = new ActiveXObject("Msxml2.XMLHTTP");

    } catch (othermicrosoft) {

      try {

        request = new ActiveXObject("Microsoft.XMLHTTP");

      } catch (failed) {

        request = false;

      }

    }

  }


  if (!request)

    alert("Error initializing XMLHttpRequest!");

}


function getCustomerInfo() {

  createRequest();

  // Do something with the request variable

}

</script>


此代碼惟一的問題是推遲了錯誤通知,這也是多數 Ajax 程序員不采用這一方法的原因。假設一個復雜的表單有 10 或 15 個字段、選擇框等,當用戶在第 14 個字段(按照表單順序從上到下)輸入文本時要激活某些 Ajax 代碼。這時候運行 getCustomerInfo() 嘗試創建一個 XMLHttpRequest 對象,但(對於本例來說)失敗了。然後向用戶顯示一條警告,明確地告訴他們不能使用該應用程序。但用戶已經花費了很多時間在表單中輸入數據!這是非常令人討厭的,而討厭顯然不會吸引用戶再次訪問您的網站。


如果使用靜態 JavaScript,用戶在點擊頁面的時候很快就會看到錯誤信息。這樣也很煩人,是不是?可能令用戶錯誤地認為您的 Web 應用程序不能在他的瀏覽器上運行。不過,當然要比他們花費了 10 分鐘輸入信息之後再顯示同樣的錯誤要好。因此,我建議編寫靜態的代碼,讓用戶盡可能早地發現問題。


用 XMLHttpRequest 發送請求


得到請求對象之後就可以進入請求/響應循環了。記住,XMLHttpRequest 惟一的目的是讓您發送請求和接收響應。其他一切都是 JavaScript、CSS 或頁面中其他代碼的工作:改變用戶界面、切換圖像、解釋服務器返回的數據。準備好 XMLHttpRequest 之後,就可以向服務器發送請求了。


歡迎使用沙箱


Ajax 采用一種沙箱安全模型。因此,Ajax 代碼(具體來說就是 XMLHttpRequest 對象)只能對所在的同一個域發送請求。以後的文章中將進一步介紹安全和 Ajax,現在只要知道在本地機器上運行的代碼只能對本地機器上的服務器端腳本發送請求。如果讓 Ajax 代碼在 http://www.breakneckpizza.com/ 上運行,則必須 http://www.breakneck.com/ 中運行的腳本發送請求。


設置服務器 URL


首先要確定連接的服務器的 URL。這並不是 Ajax 的特殊要求,但仍然是建立連接所必需的,顯然現在您應該知道如何構造 URL 了。多數應用程序中都會結合一些靜態數據和用戶處理的表單中的數據來構造該 URL。比如,清單 7 中的 JavaScript 代碼獲取電話號碼字段的值並用其構造 URL。


清單 7. 建立請求 URL



<script language="javascript" type="text/javascript">

   var request = false;

   try {

     request = new XMLHttpRequest();

   } catch (trymicrosoft) {

     try {

       request = new ActiveXObject("Msxml2.XMLHTTP");

     } catch (othermicrosoft) {

       try {

         request = new ActiveXObject("Microsoft.XMLHTTP");

       } catch (failed) {

         request = false;

       }  

     }

   }


   if (!request)

     alert("Error initializing XMLHttpRequest!");


   function getCustomerInfo() {

     var phone = document.getElementById("phone").value;

     var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);

   }

</script>

這裏沒有難懂的地方。首先,代碼創建了一個新變量 phone,並把 ID 為 “phone” 的表單字段的值賦給它。清單 8 展示了這個表單的 XHTML,其中可以看到 phone 字段及其 id 屬性。


清單 8. Break Neck Pizza 表單


<body>

  <p><img src="breakneck-logo_4c.gif" alt="Break Neck Pizza" /></p>

  <form action="POST">

   <p>Enter your phone number:

    <input type="text" size="14" name="phone" id="phone" 

           onChange="getCustomerInfo();" />

   </p>

   <p>Your order will be delivered to:</p>

   <div id="address"></div>

   <p>Type your order in here:</p>

   <p><textarea name="order" rows="6" cols="50" id="order"></textarea></p>

   <p><input type="submit" value="Order Pizza" id="submit" /></p>

  </form>

</body>

還要註意,當用戶輸入電話號碼或者改變電話號碼時,將觸發 清單 8 所示的 getCustomerInfo() 方法。該方法取得電話號碼並構造存儲在 url 變量中的 URL 字符串。記住,由於 Ajax 代碼是沙箱型的,因而只能連接到同一個域,實際上 URL 中不需要域名。該例中的腳本名為 /cgi-local/lookupCustomer.php。最後,電話號碼作為 GET 參數附加到該腳本中:"phone=" + escape(phone)。


如果以前沒用見過 escape() 方法,它用於轉義不能用明文正確發送的任何字符。比如,電話號碼中的空格將被轉換成字符 %20,從而能夠在 URL 中傳遞這些字符。


可以根據需要添加任意多個參數。比如,如果需要增加另一個參數,只需要將其附加到 URL 中並用 “與”(&)字符分開 [第一個參數用問號(?)和腳本名分開]。


打開請求


有了要連接的 URL 後就可以配置請求了。可以用 XMLHttpRequest 對象的 open() 方法來完成。該方法有五個參數:


request-type:發送請求的類型。典型的值是 GET 或 POST,但也可以發送 HEAD 請求。 

url:要連接的 URL。 

asynch:如果希望使用異步連接則為 true,否則為 false。該參數是可選的,默認為 true。 

username:如果需要身份驗證,則可以在此指定用戶名。該可選參數沒有默認值。 password:如果需要身份驗證,則可以在此指定口令。該可選參數沒有默認值。 


open() 是打開嗎?

Internet 開發人員對 open() 方法到底做什麼沒有達成一致。但它實際上並不是 打開一個請求。如果監控 XHTML/Ajax 頁面及其連接腳本之間的網絡和數據傳遞,當調用 open() 方法時將看不到任何通信。不清楚為何選用了這個名字,但顯然不是一個好的選擇。 


通常使用其中的前三個參數。事實上,即使需要異步連接,也應該指定第三個參數為 “true”。這是默認值,但堅持明確指定請求是異步的還是同步的更容易理解。


將這些結合起來,通常會得到 清單 9 所示的一行代碼。


清單 9. 打開請求


   function getCustomerInfo() {

     var phone = document.getElementById("phone").value;

     var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);

     request.open("GET", url, true);

   }

一旦設置好了 URL,其他就簡單了。多數請求使用 GET 就夠了(後面的文章中將看到需要使用 POST 的情況),再加上 URL,這就是使用 open() 方法需要的全部內容了。


挑戰異步性


本系列的後面一篇文章中,我將用很多時間編寫和使用異步代碼,但是您應該明白為什麼 open() 的最後一個參數這麼重要。在一般的請求/響應模型中,比如 Web 1.0,客戶機(瀏覽器或者本地機器上運行的代碼)向服務器發出請求。該請求是同步的,換句話說,客戶機等待服務器的響應。當客戶機等待的時候,至少會用某種形式通知您在等待:


·沙漏(特別是 Windows 上)。 

·旋轉的皮球(通常在 Mac 機器上)。 

·應用程序基本上凍結了,然後過一段時間光標變化了。 


這正是 Web 應用程序讓人感到笨拙或緩慢的原因 —— 缺乏真正的交互性。按下按鈕時,應用程序實際上變得不能使用,直到剛剛觸發的請求得到響應。如果請求需要大量服務器處理,那麼等待的時間可能很長(至少在這個多處理器、DSL 沒有等待的世界中是如此)。


而異步請求不 等待服務器響應。發送請求後應用程序繼續運行。用戶仍然可以在 Web 表單中輸入數據,甚至離開表單。沒有旋轉的皮球或者沙漏,應用程序也沒有明顯的凍結。服務器悄悄地響應請求,完成後告訴原來的請求者工作已經結束(具體的辦法很快就會看到)。結果是,應用程序感覺不 那麼遲鈍或者緩慢,而是響應迅速、交互性強,感覺快多了。這僅僅是 Web 2.0 的一部分,但它是很重要的一部分。所有老套的 GUI 組件和 Web 設計範型都不能克服緩慢、同步的請求/響應模型。


發送請求


一旦用 open() 配置好之後,就可以發送請求了。幸運的是,發送請求的方法的名稱要比 open() 適當,它就是 send()。


send() 只有一個參數,就是要發送的內容。但是在考慮這個方法之前,回想一下前面已經通過 URL 本身發送過數據了:


var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);

雖然可以使用 send() 發送數據,但也能通過 URL 本身發送數據。事實上,GET 請求(在典型的 Ajax 應用中大約占 80%)中,用 URL 發送數據要容易得多。如果需要發送安全信息或 XML,可能要考慮使用 send() 發送內容(本系列的後續文章中將討論安全數據和 XML 消息)。如果不需要通過 send() 傳遞數據,則只要傳遞 null 作為該方法的參數即可。因此您會發現在本文中的例子中只需要這樣發送請求(參見 清單 10)。


清單 10. 發送請求


   function getCustomerInfo() {

     var phone = document.getElementById("phone").value;

     var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);

     request.open("GET", url, true);

     request.send(null);

   }

指定回調方法


現在我們所做的只有很少一點是新的、革命性的或異步的。必須承認,open() 方法中 “true” 這個小小的關鍵字建立了異步請求。但是除此之外,這些代碼與用 Java servlet 及 JSP、PHP 或 Perl 編程沒有什麼兩樣。那麼 Ajax 和 Web 2.0 最大的秘密是什麼呢?秘密就在於 XMLHttpRequest 的一個簡單屬性 onreadystatechange。


首先一定要理解這些代碼中的流程(如果需要請回顧 清單 10)。建立其請求然後發出請求。此外,因為是異步請求,所以 JavaScript 方法(例子中的 getCustomerInfo())不會等待服務器。因此代碼將繼續執行,就是說,將退出該方法而把控制返回給表單。用戶可以繼續輸入信息,應用程序不會等待服務器。


這就提出了一個有趣的問題:服務器完成了請求之後會發生什麼?答案是什麼也不發生,至少對現在的代碼而言如此!顯然這樣不行,因此服務器在完成通過 XMLHttpRequest 發送給它的請求處理之後需要某種指示說明怎麼做。


在 JavaScript 中引用函數:

JavaScript 是一種弱類型的語言,可以用變量引用任何東西。因此如果聲明了一個函數 updatePage(),JavaScript 也將該函數名看作是一個變量。換句話說,可用變量名 updatePage 在代碼中引用函數。


清單 11. 設置回調方法


   function getCustomerInfo() {

     var phone = document.getElementById("phone").value;

     var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);

     request.open("GET", url, true);

     request.onreadystatechange = updatePage;

     request.send(null);

   }

需要特別註意的是該屬性在代碼中設置的位置 —— 它是在調用 send() 之前 設置的。發送請求之前必須設置該屬性,這樣服務器在回答完成請求之後才能查看該屬性。現在剩下的就只有編寫 updatePage() 方法了,這是本文最後一節要討論的重點。


處理服務器響應


發送請求,用戶高興地使用 Web 表單(同時服務器在處理請求),而現在服務器完成了請求處理。服務器查看 onreadystatechange 屬性確定要調用的方法。除此以外,可以將您的應用程序看作其他應用程序一樣,無論是否異步。換句話說,不一定要采取特殊的動作編寫響應服務器的方法,只需要改變表單,讓用戶訪問另一個 URL 或者做響應服務器需要的任何事情。這一節我們重點討論對服務器的響應和一種典型的動作 —— 即時改變用戶看到的表單中的一部分。


回調和 Ajax


現在我們已經看到如何告訴服務器完成後應該做什麼:將 XMLHttpRequest 對象的 onreadystatechange 屬性設置為要運行的函數名。這樣,當服務器處理完請求後就會自動調用該函數。也不需要擔心該函數的任何參數。我們從一個簡單的方法開始,如 清單 12 所示。


清單 12. 回調方法的代碼



<script language="javascript" type="text/javascript">

   var request = false;

   try {

     request = new XMLHttpRequest();

   } catch (trymicrosoft) {

     try {

       request = new ActiveXObject("Msxml2.XMLHTTP");

     } catch (othermicrosoft) {

       try {

         request = new ActiveXObject("Microsoft.XMLHTTP");

       } catch (failed) {

         request = false;

       }  

     }

   }


   if (!request)

     alert("Error initializing XMLHttpRequest!");


   function getCustomerInfo() {

     var phone = document.getElementById("phone").value;

     var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);

     request.open("GET", url, true);

     request.onreadystatechange = updatePage;

     request.send(null);

   }


   function updatePage() {

     alert("Server is done!");

   }

</script>

它僅僅發出一些簡單的警告,告訴您服務器什麼時候完成了任務。在自己的網頁中試驗這些代碼,然後在瀏覽器中打開(如果希望查看該例中的 XHTML,請參閱 清單 8)。輸入電話號碼然後離開該字段,將看到一個彈出的警告窗口(如 圖 3 所示),但是點擊 OK 又出現了……


圖 3. 彈出警告的 Ajax 代碼




根據瀏覽器的不同,在表單停止彈出警告之前會看到兩次、三次甚至四次警告。這是怎麼回事呢?原來我們還沒有考慮 HTTP 就緒狀態,這是請求/響應循環中的一個重要部分。


HTTP 就緒狀態


前面提到,服務器在完成請求之後會在 XMLHttpRequest 的 onreadystatechange 屬性中查找要調用的方法。這是真的,但還不完整。事實上,每當 HTTP 就緒狀態改變時它都會調用該方法。這意味著什麼呢?首先必須理解 HTTP 就緒狀態。


HTTP 就緒狀態表示請求的狀態或情形。它用於確定該請求是否已經開始、是否得到了響應或者請求/響應模型是否已經完成。它還可以幫助確定讀取服務器提供的響應文本或數據是否安全。在 Ajax 應用程序中需要了解五種就緒狀態:


·0:請求沒有發出(在調用 open() 之前)。 

·1:請求已經建立但還沒有發出(調用 send() 之前)。 

·2:請求已經發出正在處理之中(這裏通常可以從響應得到內容頭部)。 

·3:請求已經處理,響應中通常有部分數據可用,但是服務器還沒有完成響應。 

·4:響應已完成,可以訪問服務器響應並使用它。 


與大多數跨瀏覽器問題一樣,這些就緒狀態的使用也不盡一致。您也許期望任務就緒狀態從 0 到 1、2、3 再到 4,但實際上很少是這種情況。一些瀏覽器從不報告 0 或 1 而直接從 2 開始,然後是 3 和 4。其他瀏覽器則報告所有的狀態。還有一些則多次報告就緒狀態 1。在上一節中看到,服務器多次調用 updatePage(),每次調用都會彈出警告框 —— 可能和預期的不同!


對於 Ajax 編程,需要直接處理的惟一狀態就是就緒狀態 4,它表示服務器響應已經完成,可以安全地使用響應數據了。基於此,回調方法中的第一行應該如 清單 13 所示。


清單 13. 檢查就緒狀態


   function updatePage() {

     if (request.readyState == 4)

       alert("Server is done!");

   }

修改後就可以保證服務器的處理已經完成。嘗試運行新版本的 Ajax 代碼,現在就會看到與預期的一樣,只顯示一次警告信息了。


HTTP 狀態碼


雖然 清單 13 中的代碼看起來似乎不錯,但是還有一個問題 —— 如果服務器響應請求並完成了處理但是報告了一個錯誤怎麼辦?要知道,服務器端代碼應該明白它是由 Ajax、JSP、普通 HTML 表單或其他類型的代碼調用的,但只能使用傳統的 Web 專用方法報告信息。而在 Web 世界中,HTTP 代碼可以處理請求中可能發生的各種問題。


比方說,您肯定遇到過輸入了錯誤的 URL 請求而得到 404 錯誤碼的情形,它表示該頁面不存在。這僅僅是 HTTP 請求能夠收到的眾多錯誤碼中的一種(完整的狀態碼列表請參閱 參考資料 中的鏈接)。表示所訪問數據受到保護或者禁止訪問的 403 和 401 也很常見。無論哪種情況,這些錯誤碼都是從完成的響應 得到的。換句話說,服務器履行了請求(即 HTTP 就緒狀態是 4)但是沒有返回客戶機預期的數據。


因此除了就緒狀態外,還需要檢查 HTTP 狀態。我們期望的狀態碼是 200,它表示一切順利。如果就緒狀態是 4 而且狀態碼是 200,就可以處理服務器的數據了,而且這些數據應該就是要求的數據(而不是錯誤或者其他有問題的信息)。因此還要在回調方法中增加狀態檢查,如 清單 14 所示。


清單 14. 檢查 HTTP 狀態碼


   function updatePage() {

     if (request.readyState == 4)

       if (request.status == 200)

         alert("Server is done!");

   }

為了增加更健壯的錯誤處理並盡量避免過於復雜,可以增加一兩個狀態碼檢查,請看一看 清單 15 中修改後的 updatePage() 版本。


清單 15. 增加一點錯誤檢查


   function updatePage() {

     if (request.readyState == 4)

       if (request.status == 200)

         alert("Server is done!");

       else if (request.status == 404)

         alert("Request URL does not exist");

       else

         alert("Error: status code is " + request.status);

   }

現在將 getCustomerInfo() 中的 URL 改為不存在的 URL 看看會發生什麼。應該會看到警告信息說明要求的 URL 不存在 —— 好極了!很難處理所有的錯誤條件,但是這一小小的改變能夠涵蓋典型 Web 應用程序中 80% 的問題。


讀取響應文本


現在可以確保請求已經處理完成(通過就緒狀態),服務器給出了正常的響應(通過狀態碼),最後我們可以處理服務器返回的數據了。返回的數據保存在 XMLHttpRequest 對象的 responseText 屬性中。


關於 responseText 中的文本內容,比如格式和長度,有意保持含糊。這樣服務器就可以將文本設置成任何內容。比方說,一種腳本可能返回逗號分隔的值,另一種則使用管道符(即 | 字符)分隔的值,還有一種則返回長文本字符串。何去何從由服務器決定。


在本文使用的例子中,服務器返回客戶的上一個訂單和客戶地址,中間用管道符分開。然後使用訂單和地址設置表單中的元素值,清單 16 給出了更新顯示內容的代碼。


清單 16. 處理服務器響應


   function updatePage() {

     if (request.readyState == 4) {

       if (request.status == 200) {

         var response = request.responseText.split("|");

         document.getElementById("order").value = response[0];

         document.getElementById("address").innerHTML =

           response[1].replace(/\n/g, "");

       } else

         alert("status is " + request.status);

     }

   }

首先,得到 responseText 並使用 JavaScript split() 方法從管道符分開。得到的數組放到 response 中。數組中的第一個值 —— 上一個訂單 —— 用 response[0] 訪問,被設置為 ID 為 “order” 的字段的值。第二個值 response[1],即客戶地址,則需要更多一點處理。因為地址中的行用一般的行分隔符(“\n”字符)分隔,代碼中需要用 XHTML 風格的行分隔符 <br /> 來代替。替換過程使用 replace() 函數和正則表達式完成。最後,修改後的文本作為 HTML 表單 div 中的內部 HTML。結果就是表單突然用客戶信息更新了,如圖 4 所示。


圖 4. 收到客戶數據後的 Break Neck 表單




結束本文之前,我還要介紹 XMLHttpRequest 的另一個重要屬性 responseXML。如果服務器選擇使用 XML 響應則該屬性包含(也許您已經猜到)XML 響應。處理 XML 響應和處理普通文本有很大不同,涉及到解析、文檔對象模型(DOM)和其他一些問題。後面的文章中將進一步介紹 XML。但是因為 responseXML 通常和 responseText 一起討論,這裏有必要提一提。對於很多簡單的 Ajax 應用程序 responseText 就夠了,但是您很快就會看到通過 Ajax 應用程序也能很好地處理 XML。


結束語


您可能對 XMLHttpRequest 感到有點厭倦了,我很少看到一整篇文章討論一個對象,特別是這種簡單的對象。但是您將在使用 Ajax 編寫的每個頁面和應用程序中反復使用該對象。坦白地說,關於 XMLHttpRequest 還真有一些可說的內容。下一期文章中將介紹如何在請求中使用 POST 及 GET,來設置請求中的內容頭部和從服務器響應讀取內容頭部,理解如何在請求/響應模型中編碼請求和處理 XML。


再往後我們將介紹常見 Ajax 工具箱。這些工具箱實際上隱藏了本文所述的很多細節,使得 Ajax 編程更容易。您也許會想,既然有這麼多工具箱為何還要對底層的細節編碼。答案是,如果不知道應用程序在做什麼,就很難發現應用程序中的問題。


因此不要忽略這些細節或者簡單地瀏覽一下,如果便捷華麗的工具箱出現了錯誤,您就不必撓頭或者發送郵件請求支持了。如果了解如何直接使用 XMLHttpRequest,就會發現很容易調試和解決最奇怪的問題。只有讓其解決您的問題,工具箱才是好東西。


因此請熟悉 XMLHttpRequest 吧。事實上,如果您有使用工具箱的 Ajax 代碼,可以嘗試使用 XMLHttpRequest 對象及其屬性和方法重新改寫。這是一種不錯的練習,可以幫助您更好地理解其中的原理。


下一期文章中將進一步討論該對象,探討它的一些更有趣的屬性(如 responseXML),以及如何使用 POST 請求和以不同的格式發送數據。請開始編寫代碼吧,一個月後我們再繼續討論。


第 3 頁 Ajax 中的高級請求和響應


對於很多 Web 開發人員來說,只需要生成簡單的請求並接收簡單的響應即可;但是對於希望掌握 Ajax 的開發人員來說,必須要全面理解 HTTP 狀態代碼、就緒狀態和 XMLHttpRequest 對象。在本文中,Brett McLaughlin 將向您介紹各種狀態代碼,並展示瀏覽器如何對其進行處理,本文還給出了在 Ajax 中使用的比較少見的 HTTP 請求。


在本系列的 上篇文章 中,我們將詳細介紹 XMLHttpRequest 對象,它是 Ajax 應用程序的中心,負責處理服務器端應用程序和腳本的請求,並處理從服務器端組件返回的數據。由於所有的 Ajax 應用程序都要使用 XMLHttpRequest 對象,因此您可能會希望熟悉這個對象,從而能夠讓 Ajax 執行得更好。


在本文中,我將在上一篇文章的基礎上重點介紹這個請求對象的 3 個關鍵部分的內容:


·HTTP 就緒狀態

·HTTP 狀態代碼

·可以生成的請求類型


這三部分內容都是在構造一個請求時所要考慮的因素;但是介紹這些主題的內容太少了。然而,如果您不僅僅是想了解 Ajax 編程的常識,而是希望了解更多內容,就需要熟悉就緒狀態、狀態代碼和請求本身的內容。當應用程序出現問題時 —— 這種問題總是存在 —— 那麼如果能夠正確理解就緒狀態、如何生成一個 HEAD 請求或者 400 的狀態代碼的確切含義,就可以在 5 分鐘內調試出問題,而不是在各種挫折和困惑中度過 5 個小時。


下面讓我們首先來看一下 HTTP 就緒狀態。


深入了解 HTTP 就緒狀態


您應該還記得在上一篇文章中 XMLHttpRequest 對象有一個名為 readyState 的屬性。這個屬性確保服務器已經完成了一個請求,通常會使用一個回調函數從服務器中讀出數據來更新 Web 表單或頁面的內容。清單 1 給出了一個簡單的例子(這也是本系列的上一篇文章中的一個例子 —— 請參見 參考資料)。




XMLHttpRequest 或 XMLHttp:換名玫瑰


Microsoft? 和 Internet Explorer 使用了一個名為 XMLHttp 的對象,而不是 XMLHttpRequest 對象,而 Mozilla、Opera、Safari 和 大部分非 Microsoft 瀏覽器都使用的是後者。為了簡單性起見,我將這兩個對象都簡單地稱為 XMLHttpRequest。這既符合我們在 Web 上看到的情況,又符合 Microsoft 在 Internet Explorer 7.0 中使用 XMLHttpRequest 作為請求對象的意圖。(有關這個問題的更多內容,請參見 第 2 部分。)


清單 1. 在回調函數中處理服務器的響應


function updatePage() {

   if (request.readyState == 4) {

     if (request.status == 200) {

       var response = request.responseText.split("|");

       document.getElementById("order").value = response[0];

       document.getElementById("address").innerHTML =

         response[1].replace(/\n/g, "<br />");

     } else

       alert("status is " + request.status);

   }

}

這顯然是就緒狀態最常見(也是最簡單)的用法。正如您從數字 "4" 中可以看出的一樣,還有其他幾個就緒狀態(您在上一篇文章中也看到過這個清單 —— 請參見 參考資料):


·0:請求未初始化(還沒有調用 open())。

·1:請求已經建立,但是還沒有發送(還沒有調用 send())。

·2:請求已發送,正在處理中(通常現在可以從響應中獲取內容頭)。

·3:請求在處理中;通常響應中已有部分數據可用了,但是服務器還沒有完成響應的生成。

·4:響應已完成;您可以獲取並使用服務器的響應了。


如果您希望不僅僅是了解 Ajax 編程的基本知識,那麼就不但需要知道這些狀態,了解這些狀態是何時出現的,以及如何來使用這些狀態。首先,您需要學習在每種就緒狀態下可能碰到的是哪種請求狀態。不幸的是,這一點並不直觀,而且會涉及幾種特殊的情況。


隱秘就緒狀態


第一種就緒狀態的特點是 readyState 屬性為 0(readyState == 0),表示未初始化狀態。一旦對請求對象調用 open() 之後,這個屬性就被設置為 1。由於您通常都是在一對請求進行初始化之後就立即調用 open(),因此很少會看到 readyState == 0 的狀態。另外,未初始化的就緒狀態在實際的應用程序中是沒有真正的用處的。


不過為了滿足我們的興趣,請參見 清單 2 的內容,其中顯示了如何在 readyState 被設置為 0 時來獲取這種就緒狀態。


清單 2. 獲取 0 就緒狀態



   function getSalesData() {

     // Create a request object

     createRequest();  

     alert("Ready state is: " + request.readyState);


     // Setup (initialize) the request

     var url = "/boards/servlet/UpdateBoardSales";

     request.open("GET", url, true);

     request.onreadystatechange = updatePage;

     request.send(null);

   }

在這個簡單的例子中,getSalesData() 是 Web 頁面調用來啟動請求(例如點擊一個按鈕時)所使用的函數。註意您必須在調用 open()之前 來查看就緒狀態。圖 1 給出了運行這個應用程序的結果。


圖 1. 就緒狀態 0




顯然,這並不能為您帶來多少好處;需要確保 尚未 調用 open() 函數的情況很少。在大部分 Ajax 編程的真實情況中,這種就緒狀態的唯一用法就是使用相同的 XMLHttpRequest 對象在多個函數之間生成多個請求。在這種(不常見的)情況中,您可能會在生成新請求之前希望確保請求對象是處於未初始化狀態(readyState == 0)。這實際上是要確保另外一個函數沒有同時使用這個對象。


查看正在處理的請求的就緒狀態


除了 0 就緒狀態之外,請求對象還需要依次經歷典型的請求和響應的其他幾種就緒狀態,最後才以就緒狀態 4 的形式結束。這就是為什麼您在大部分回調函數中都可以看到 if (request.readyState == 4) 這行代碼;它確保服務器已經完成對請求的處理,現在可以安全地更新 Web 頁面或根據從服務器返回來的數據來進行操作了。


要查看這種狀態發生的過程非常簡單。如果就緒狀態為 4,我們不僅要運行回調函數中的代碼,而且還要在每次調用回調函數時都輸出就緒狀態。 清單 3 給出了一個實現這種功能的例子。


當 0 等於 4 時


在多個 JavaScript 函數都使用相同的請求對象時,您需要檢查就緒狀態 0 來確保這個請求對象沒有正在使用,這種機制會產生問題。由於 readyState == 4 表示一個已完成的請求,因此您經常會發現那些目前沒在使用的處於就緒狀態的請求對象仍然被設置成了 4 —— 這是因為從服務器返回來的數據已經使用過了,但是從它們被設置為就緒狀態之後就沒有進行任何變化。有一個函數 abort() 會重新設置請求對象,但是這個函數卻不是真正為了這個目的而使用的。如果您 必須 使用多個函數,最好是為每個函數都創建並使用一個函數,而不是在多個函數之間共享相同的對象。


清單 3. 查看就緒狀態


   function updatePage() {

     // Output the current ready state

     alert("updatePage() called with ready state of " + request.readyState);

   }

如果您不確定如何運行這個函數,就需要創建一個函數,然後在 Web 頁面中調用這個函數,並讓它向服務器端的組件發送一個請求(例如 清單 2 給出的函數,或本系列文章的第 1 部分和第 2 部分中給出的例子)。確保在建立請求時,將回調函數設置為 updatePage();要實現這種設置,可以將請求對象的 onreadystatechange 屬性設置為 updatePage()。


這段代碼就是 onreadystatechange 意義的一個確切展示 —— 每次請求的就緒狀態發生變化時,就調用 updatePage(),然後我們就可以看到一個警告了。圖 2 給出了一個調用這個函數的例子,其中就緒狀態為 1。


圖 2. 就緒狀態 1




您可以自己嘗試運行這段代碼。將其放入 Web 頁面中,然後激活事件處理程序(單擊按鈕,在域之間按 tab 鍵切換焦點,或者使用設置的任何方法來觸發請求)。這個回調函數會運行多次 —— 每次就緒狀態都會改變 —— 您可以看到每個就緒狀態的警告。這是跟蹤請求所經歷的各個階段的最好方法。


瀏覽器的不一致性


在對這個過程有一個基本的了解之後,請試著從幾個不同的瀏覽器中訪問您的頁面。您應該會註意到各個瀏覽器如何處理這些就緒狀態並不一致。例如,在 Firefox 1.5 中,您會看到以下就緒狀態:


·1

·2

·3

·4


這並不奇怪,因為每個請求狀態都在這裏表示出來了。然而,如果您使用 Safari 來訪問相同的應用程序,就應該看到 —— 或者看不到 —— 一些有趣的事情。下面是在 Safari 2.0.1 中看到的狀態:


·2

·3

·4


Safari 實際上把第一個就緒狀態給丟棄了,也並沒有什麼明顯的原因說明為什麼要這樣做;不過這就是 Safari 的工作方式。這還說明了一個重要的問題:盡管在使用服務器上的數據之前確保請求的狀態為 4 是一個好主意,但是依賴於每個過渡期就緒狀態編寫的代碼的確會在不同的瀏覽器上得到不同的結果。


例如,在使用 Opera 8.5 時,所顯示的就緒狀態情況就更加糟糕了:


·3

·4


最後,Internet Explorer 會顯示如下狀態:


·1

·2

·3

·4


如果您碰到請求方面的問題,這就是用來發現問題的 首要之處。最好的方式是在 Internet Explorer 和 Firefox 都進行一下測試 —— 您會看到所有這 4 種狀態,並可以檢查請求的每個狀態所處的情況。


接下來我們再來看一下響應端的情況。


顯微鏡下的響應數據


一旦我們理解在請求過程中發生的各個就緒狀態之後,接下來就可以來看一下 XMLHttpRequest 對象的另外一個方面了 —— responseText 屬性。回想一下在上一篇文章中我們介紹過的內容,就可以知道這個屬性用來從服務器上獲取數據。一旦服務器完成對請求的處理之後,就可以將響應請求數據所需要的任何數據放到請求的 responseText 中了。然後回調函數就可以使用這些數據,如 清單 1 和 清單 4 所示。


清單 4. 使用服務器上返回的響應



   function updatePage() {

     if (request.readyState == 4) {

       var newTotal = request.responseText;

       var totalSoldEl = document.getElementById("total-sold");

       var netProfitEl = document.getElementById("net-profit");

       replaceText(totalSoldEl, newTotal);


       /* 圖 out the new net profit */

       var boardCostEl = document.getElementById("board-cost");

       var boardCost = getText(boardCostEl);

       var manCostEl = document.getElementById("man-cost");

       var manCost = getText(manCostEl);

       var profitPerBoard = boardCost - manCost;

       var netProfit = profitPerBoard * newTotal;


       /* Update the net profit on the sales form */

       netProfit = Math.round(netProfit * 100) / 100;

       replaceText(netProfitEl, netProfit);

     }

清單 1 相當簡單;清單 4 稍微有點復雜,但是它們在開始時都要檢查就緒狀態,並獲取 responseText 屬性的值。


查看請求的響應文本


與就緒狀態類似,responseText 屬性的值在整個請求的生命周期中也會發生變化。要查看這種變化,請使用如 清單 5 所示的代碼來測試請求的響應文本,以及它們的就緒狀態。


清單 5. 測試 responseText 屬性


   function updatePage() {

     // Output the current ready state

     alert("updatePage() called with ready state of " + request.readyState +

           " and a response text of '" + request.responseText + "'");

     }

現在在瀏覽器中打開 Web 應用程序,並激活您的請求。要更好地看到這段代碼的效果,請使用 Firefox 或 Internet Explorer,因為這兩個瀏覽器都可以報告出請求過程中所有可能的就緒狀態。例如在就緒狀態 2 中,就沒有定義 responseText (請參見 圖 3);如果 JavaScript 控制臺也已經打開了,您就會看到一個錯誤。


圖 3. 就緒狀態為 2 的響應文本




不過在就緒狀態 3 中,服務器已經在 responseText 屬性中放上了一個值,至少在這個例子中是這樣(請參見 圖 4)。


圖 4. 就緒狀態為 3 的響應文本




您會看到就緒狀態為 3 的響應在每個腳本、每個服務器甚至每個瀏覽器上都是不一樣的。不過,這在調試應用程序中依然是非常有用的。


獲取安全數據


所有的文檔和規範都強調,只有在就緒狀態為 4 時數據才可以安全使用。相信我,當就緒狀態為 3 時,您很少能找到無法從 responseText 屬性獲取數據的情況。然而,在應用程序中將自己的邏輯依賴於就緒狀態 3 可不是什麼好主意 —— 一旦您編寫了依賴於就緒狀態 3 的完整數據的的代碼,幾乎就要自己來負責當時的數據不完整問題了。


比較好的做法是向用戶提供一些反饋,說明在處於就緒狀態 3 時,很快就會有響應了。盡管使用 alert() 之類的函數顯然不是什麼好主意 —— 使用 Ajax 然後使用一個警告對話框來阻塞用戶顯然是錯誤的 —— 不過您可以在就緒狀態發生變化時更新表單或頁面中的域。例如,對於就緒狀態 1 來說要將進度指示器的寬度設置為 25%,對於就緒狀態 2 來說要將進度指示器的寬度設置為 50%,對於就緒狀態 3 來說要將進度指示器的寬度設置為 75%,當就緒狀態為 4 時將進度指示器的寬度設置為 100%(完成)。


當然,正如您已經看到的一樣,這種方法非常聰明,但它是依賴於瀏覽器的。在 Opera 上,您永遠都不會看到前兩個就緒狀態,而在 Safari 上則沒有第一個(1)。由於這個原因,我將這段代碼留作練習,而沒有在本文中包括進來。


現在應該來看一下狀態代碼了。


深入了解 HTTP 狀態代碼


有了就緒狀態和您在 Ajax 編程技術中學習到的服務器的響應,您就可以為 Ajax 應用程序添加另外一級復雜性了 —— 這要使用 HTTP 狀態代碼。這些代碼對於 Ajax 來說並沒有什麼新鮮。從 Web 出現以來,它們就已經存在了。在 Web 瀏覽器中您可能已經看到過幾個狀態代碼:


·401:未經授權

·403:禁止

·404:沒找到


您可以找到更多的狀態代碼(完整清單請參見 參考資料)。要為 Ajax 應用程序另外添加一層控制和響應(以及更為健壯的錯誤處理)機制,您需要適當地查看請求和響應中的狀態代碼。


200:一切正常


在很多 Ajax 應用程序中,您將看到一個回調函數,它負責檢查就緒狀態,然後繼續利用從服務器響應中返回的數據,如 清單 6 所示。


清單 6. 忽略狀態代碼的回調函數


   function updatePage() {

     if (request.readyState == 4) {

       var response = request.responseText.split("|");

       document.getElementById("order").value = response[0];

       document.getElementById("address").innerHTML =

         response[1].replace(/\n/g, "<br />");

     }

   }

這對於 Ajax 編程來說證明是一種短視而錯誤的方法。如果腳本需要認證,而請求卻沒有提供有效的證書,那麼服務器就會返回諸如 403 或 401 之類的錯誤代碼。然而,由於服務器對請求進行了應答,因此就緒狀態就被設置為 4(即使應答並不是請求所期望的也是如此)。最終,用戶沒有獲得有效數據,當 JavaScript 試圖使用不存在的服務器數據時就可能會出現嚴重的錯誤。


它花費了最小的努力來確保服務器不但完成了一個請求,而且還返回了一個 “一切良好” 的狀態代碼。這個代碼是 "200",它是通過 XMLHttpRequest 對象的 status 屬性來報告的。為了確保服務器不但完成了一個請求,而且還報告了一個 OK 狀態,請在您的回調函數中添加另外一個檢查功能,如 清單 7 所示。


清單 7. 檢查有效狀態代碼


   function updatePage() {

     if (request.readyState == 4) {

       if (request.status == 200) {

         var response = request.responseText.split("|");

         document.getElementById("order").value = response[0];

         document.getElementById("address").innerHTML =

           response[1].replace(/\n/g, "<br />");

       } else

         alert("status is " + request.status);

     }

   }

通過添加這幾行代碼,您就可以確認是否存在問題,用戶會看到一個有用的錯誤消息,而不僅僅是看到一個由斷章取義的數據所構成的頁面,而沒有任何解釋。


重定向和重新路由


在深入介紹有關錯誤的內容之前,我們有必要來討論一下有關一個在使用 Ajax 時 並不需要 關心的問題 —— 重定向。在 HTTP 狀態代碼中,這是 300 系列的狀態代碼,包括:


·301:永久移動

·302:找到(請求被重新定向到另外一個 URL/URI 上)

·305:使用代理(請求必須使用一個代理來訪問所請求的資源)


Ajax 程序員可能並不太關心有關重定向的問題,這是由於兩方面的原因:


·首先,Ajax 應用程序通常都是為一個特定的服務器端腳本、servlet 或應用程序而編寫的。對於那些您看不到就消失了的組件來說,Ajax 程序員就不太清楚了。因此有時您會知道資源已經移動了(因為您移動了它,或者通過某種手段移動了它),接下來要修改請求中的 URL,並且不會再碰到這種結果了。

更為重要的一個原因是:Ajax 應用程序和請求都是封裝在沙盒中的。這就意味著提供生成 Ajax 請求的 Web 頁面的域必須是對這些請求進行響應的域。因此 ebay.com 所提供的 Web 頁面就不能對一個在 amazon.com 上運行的腳本生成一個 Ajax 風格的請求;在 ibm.com 上的 Ajax 應用程序也無法對在 netbeans.org 上運行的 servlets 發出請求。

·結果是您的請求無法重定向到其他服務器上,而不會產生安全性錯誤。在這些情況中,您根本就不會得到狀態代碼。通常在調試控制臺中都會產生一個 JavaScript 錯誤。因此,在對狀態代碼進行充分的考慮之後,您就可以完全忽略重定向代碼的問題了。


結果是您的請求無法重定向到其他服務器上,而不會產生安全性錯誤。在這些情況中,您根本就不會得到狀態代碼。通常在調試控制臺中都會產生一個 JavaScript 錯誤。因此,在對狀態代碼進行充分的考慮之後,您就可以完全忽略重定向代碼的問題了。


錯誤


一旦接收到狀態代碼 200 並且意識到可以很大程度上忽略 300 系列的狀態代碼之後,所需要擔心的唯一一組代碼就是 400 系列的代碼了,這說明了不同類型的錯誤。回頭再來看一下 清單 7,並註意在對錯誤進行處理時,只將少數常見的錯誤消息輸出給用戶了。盡管這是朝正確方向前進的一步,但是要告訴從事應用程序開發的用戶和程序員究竟發生了什麼問題,這些消息仍然是沒有太大用處的。


首先,我們要添加對找不到的頁的支持。實際上這在大部分產品系統中都不應該出現,但是在測試腳本位置發生變化或程序員輸入了錯誤的 URL 時,這種情況並不罕見。如果您可以自然地報告 404 錯誤,就可以為那些困擾不堪的用戶和程序員提供更多幫助。例如,如果服務器上的一個腳本被刪除了,我們就可以使用 清單 7 中的代碼,這樣用戶就會看到一個如 圖 5 所示的非描述性錯誤。


邊界情況和困難情況


看到現在,一些新手程序員就可能會這究竟是要討論什麼內容。有一點事實大家需要知道:只有不到 5% 的 Ajax 請求需要使用諸如 2、3 之類的就緒狀態和諸如 403 之類的狀態代碼(實際上,這個比率可能更接近於 1% 甚至更少)。這些情況非常重要,稱為 邊界情況(edge case) —— 它們只會在一些非常特殊的情況下發生,其中遇到的都是最奇特的問題。雖然這些情況並不普遍,但是這些邊界情況卻占據了大部分用戶所碰到的問題的 80%!


對於典型的用戶來說,應用程序 100 次都是正常工作的這個事實通常都會被忘記,然而應用程序只要一次出錯就會被他們清楚地記住。如果您可以很好地處理邊界情況(或困難情況),就可以為再次訪問站點的用戶提供滿意的回報。


圖 5. 常見錯誤處理




用戶無法判斷問題究竟是認證問題、沒找到腳本(此處就是這種情況)、用戶錯誤還是代碼中有些地方產生了問題。添加一些簡單的代碼可以讓這個錯誤更加具體。請參照 清單 8,它負責處理沒找到的腳本或認證發生錯誤的情況,在出現這些錯誤時都會給出具體的消息。


清單 8. 檢查有效狀態代碼


   function updatePage() {

     if (request.readyState == 4) {

       if (request.status == 200) {

         var response = request.responseText.split("|");

         document.getElementById("order").value = response[0];

         document.getElementById("address").innerHTML =

           response[1].replace(/\n/g, "<br />");

       } else if (request.status == 404) {

         alert ("Requested URL is not found.");

       } else if (request.status == 403) {

         alert("Access denied.");

       } else

         alert("status is " + request.status);

     }

   }

雖然這依然相當簡單,但是它的確多提供了一些有用的信息。圖 6 給出了與 圖 5 相同的錯誤,但是這一次錯誤處理代碼向用戶或程序員更好地說明了究竟發生了什麼。


圖 6. 特殊錯誤處理




在我們自己的應用程序中,可以考慮在發生認證失敗的情況時清除用戶名和密碼,並向屏幕上添加一條錯誤消息。我們可以使用類似的方法來更好地處理找不到腳本或其他 400 類型的錯誤(例如 405 表示不允許使用諸如發送 HEAD 請求之類不可接受的請求方法,而 407 則表示需要進行代理認證)。然而不管采用哪種選擇,都需要從對服務器上返回的狀態代碼開始入手進行處理。


其他請求類型


如果您真希望控制 XMLHttpRequest 對象,可以考慮最後實現這種功能 —— 將 HEAD 請求添加到指令中。在前兩篇文章中,我們已經介紹了如何生成 GET 請求;在馬上就要發表的一篇文章中,您會學習有關使用 POST 請求將數據發送到服務器上的知識。不過本著增強錯誤處理和信息搜集的精神,您應該學習如何生成 HEAD 請求。


生成請求


實際上生成 HEAD 請求非常簡單;您可以使用 "HEAD"(而不是 "GET" 或 "POST")作為第一個參數來調用 open() 方法,如 清單 9 所示。


清單 9. 使用 Ajax 生成一個 HEAD 請求


   function getSalesData() {

     createRequest();

     var url = "/boards/servlet/UpdateBoardSales";

     request.open("HEAD", url, true);

     request.onreadystatechange = updatePage;

     request.send(null);

   }

當您這樣生成一個 HEAD 請求時,服務器並不會像對 GET 或 POST 請求一樣返回一個真正的響應。相反,服務器只會返回資源的 頭(header),這包括響應中內容最後修改的時間、請求資源是否存在和很多其他有用信息。您可以在服務器處理並返回資源之前使用這些信息來了解有關資源的信息。


對於這種請求您可以做的最簡單的事情就是簡單地輸出所有的響應頭的內容。這可以讓您了解通過 HEAD 請求可以使用什麼。清單 10 提供了一個簡單的回調函數,用來輸出從 HEAD 請求中獲得的響應頭的內容。


清單 10. 輸出從 HEAD 請求中獲得的響應頭的內容


   function updatePage() {

     if (request.readyState == 4) {

       alert(request.getAllResponseHeaders());

     }

   }

請參見 圖 7,其中顯示了從一個向服務器發出的 HEAD 請求的簡單 Ajax 應用程序返回的響應頭。




您可以單獨使用這些頭(從服務器類型到內容類型)在 Ajax 應用程序中提供其他信息或功能。


檢查 URL


您已經看到了當 URL 不存在時應該如何檢查 404 錯誤。如果這變成一個常見的問題 —— 可能是缺少了一個特定的腳本或 servlet —— 那麼您就可能會希望在生成完整的 GET 或 POST 請求之前來檢查這個 URL。要實現這種功能,生成一個 HEAD 請求,然後在回調函數中檢查 404 錯誤;清單 11 給出了一個簡單的回調函數。


清單 11. 檢查某個 URL 是否存在


   function updatePage() {

     if (request.readyState == 4) {

       if (request.status == 200) {

         alert("URL exists");

       } else if (request.status == 404) {

         alert("URL does not exist.");

       } else {

         alert("Status is: " + request.status);

       }

     }

   }

誠實地說,這段代碼的價值並不太大。服務器必須對請求進行響應,並構造一個響應來填充內容長度的響應頭,因此並不能節省任何處理時間。另外,這花費的時間與生成請求並使用 HEAD 請求來查看 URL 是否存在所需要的時間一樣多,因為它要生成使用 GET 或 POST 的請求,而不僅僅是如 清單 7 所示一樣來處理錯誤代碼。不過,有時確切地了解目前什麼可用也是非常有用的;您永遠不會知道何時創造力就會迸發或者何時需要 HEAD 請求!


有用的 HEAD 請求


您會發現 HEAD 請求非常有用的一個領域是用來查看內容的長度或內容的類型。這樣可以確定是否需要發回大量數據來處理請求,和服務器是否試圖返回二進制數據,而不是 HTML、文本或 XML(在 JavaScript 中,這 3 種類型的數據都比二進制數據更容易處理)。


在這些情況中,您只使用了適當的頭名,並將其傳遞給 XMLHttpRequest 對象的 getResponseHeader() 方法。因此要獲取響應的長度,只需要調用 request.getResponseHeader("Content-Length");。要獲取內容類型,請使用 request.getResponseHeader("Content-Type");。


在很多應用程序中,生成 HEAD 請求並沒有增加任何功能,甚至可能會導致請求速度變慢(通過強制生成一個 HEAD 請求來獲取有關響應的數據,然後在使用一個 GET 或 POST 請求來真正獲取響應)。然而,在出現您不確定有關腳本或服務器端組件的情況時,使用 HEAD 請求可以獲取一些基本的數據,而不需要對響應數據真正進行處理,也不需要大量的帶寬來發送響應。


結束語


對於很多 Ajax 和 Web 程序員來說,本文中介紹的內容似乎是太高級了。生成 HEAD 請求的價值是什麼呢?到底在什麼情況下需要在 JavaScript 中顯式地處理重定向狀態代碼呢?這些都是很好的問題;對於簡單的應用程序來說,答案是這些高級技術的價值並不是非常大。


然而,Web 已經不再是只需實現簡單應用程序的地方了;用戶已經變得更加高級,客戶期望能夠獲得更好的穩定性、更高級的錯誤報告,如果應用程序有 1% 的時間停機,那麼經理就可能會因此而被解雇。


因此您的工作就不能僅僅局限於簡單的應用程序了,而是需要更深入理解 XMLHttpRequest。


·如果您可以考慮各種就緒狀態 —— 並且理解了這些就緒狀態在不同瀏覽器之間的區別 —— 就可以快速調試應用程序了。您甚至可以基於就緒狀態而開發一些創造性的功能,並向用戶和客戶回報請求的狀態。

·如果您要對狀態代碼進行控制,就可以設置應用程序來處理腳本錯誤、非預期的響應以及邊緣情況。結果是應用程序在所有的時間都可以正常工作,而不僅僅是只能一切都正常的情況下才能運行。

·增加這種生成 HEAD 請求的能力,檢查某個 URL 是否存在,以及確認某個文件是否被修改過,這樣就可以確保用戶可以獲得有效的頁面,用戶所看到的信息都是最新的,(最重要的是)讓他們驚訝這個應用程序是如何健壯和通用。

本文的目的並非是要讓您的應用程序顯得十分華麗,而是幫助您去掉黃色聚光燈後重點昭顯文字的美麗,或者外觀更像桌面一樣。盡管這些都是 Ajax 的功能(在後續幾篇文章中就會介紹),不過它們卻像是蛋糕表面的一層奶油。如果您可以使用 Ajax 來構建一個堅實的基礎,讓應用程序可以很好地處理錯誤和問題,用戶就會返回您的站點和應用程序。在接下來的文章中,我們將添加這種直觀的技巧,這會讓客戶興奮得發抖。

第 4 頁 利用 DOM 進行 Web 響應


程序員(使用後端應用程序)和 Web 程序員(編寫 HTML、CSS 和 JavaScript)之間的分水嶺是長久存在的。但是,Document Object Model (DOM) 彌補了這個裂縫,使得在後端使用 XML 同時在前端使用 HTML 切實可行,並成為極其有效的工具。在本文中,Brett McLaughlin 介紹了 Document Object Model,解釋它在 Web 頁面中的應用,並開始挖掘其在 JavaScript 中的用途。


與許多 Web 程序員一樣,您可能使用過 HTML。HTML 是程序員開始與 Web 頁面打交道的方式;HTML 通常是他們完成應用程序或站點前的最後一步——調整一些布局、顏色或樣式。不過,雖然經常使用 HTML,但對於 HTML 轉到瀏覽器呈現在屏幕上時到底發生了什麼,人們普遍存在誤解。在我分析您認為可能發生的事情及其可能錯誤的原因之前,我希望您對設計和服務 Web 頁面時涉及的過程一清二楚:


1、一些人(通常是您!)在文本編輯器或 IDE 中創建 HTML。

2、然後您將 HTML 上載到 Web 服務器,例如 Apache HTTPD,並將其公開在 Internet 或 intranet 上。

3、用戶用 Firefox 或 SafariA 等瀏覽器請求您的 Web 頁面。

4、用戶的瀏覽器向您的服務器請求 HTML。

5、瀏覽器將從服務器接收到的頁面以圖形和文本方式呈現;用戶看到並激活 Web 頁面。


這看起來非常基礎,但事情很快會變得有趣起來。事實上,步驟 4 和步驟 5 之間發生的巨大數量的 “填充物(stuff)” 就是本文的焦點。術語 “填充物” 也十分適用,因為多數程序員從來沒有真正考慮過當用戶瀏覽器請求顯示標記時到底在標記身上發生了什麼。 


·是否瀏覽器只是讀取 HTML 中的文本並將其顯示?

·CSS 呢?尤其是當 CSS 位於外部文件時。

·JavaScript 呢?它也通常位於外部文件中。

·瀏覽器如何處理這些項,如果將事件處理程序、函數和樣式映射到該文本標記?


實踐證明,所有這些問題的答案都是 Document Object Model。因此,廢話少說,直接研究 DOM。


Web 程序員和標記


對於多數程序員,當 Web 瀏覽器開始時他們的工作就結束了。也就是說,將一個 HTML 文件放入 Web 瀏覽器的目錄上後,您通常就認為它已經“完成”,而且(滿懷希望地)認為再也不會考慮它!說到編寫幹凈、組織良好的頁面時,這也是一個偉大的目標;希望您的標記跨瀏覽器、用各種版本的 CSS 和 JavaScript 顯示它應該顯示的內容,一點錯都沒有。


問題是這種方法限制了程序員對瀏覽器中真正發生的事情的理解。更重要的是,它限制了您用客戶端 JavaScript 動態更新、更改和重構 Web 頁面的能力。擺脫這種限制,讓您的 Web 站點擁有更大的交互性和創造性。


程序員做什麼


作為典型的 Web 程序員,您可能啟動文本編輯和 IDE 後就開始輸入 HTML、CSS 甚至 JavaScript。很容易認為這些標記、選擇器和屬性只是使站點正確顯示而做的小小的任務。但是,在這一點上您需要拓展您的思路,要意識到您是在組織您的內容。不要擔心;我保證這不會變成關於標記美觀、您必須如何認識到 Web 頁面的真正潛力或其他任何元物質的講座。您需要了解的是您在 Web 開發中到底是什麼角色。


說到頁面的外觀,頂多您只能提提建議。您提供 CSS 樣式表時,用戶可以覆蓋您的樣式選擇。您提供字體大小時,用戶瀏覽器可以為視障者更改這些大小,或者在大顯示器(具有同等大的分辨率)上按比例縮小。甚至您選擇的顏色和字體也受制於用戶顯示器和用戶在其系統上安裝的字體。雖然盡您所能來設計頁面樣式很不錯,但這絕不是 您對 Web 頁面的最大影響。


您絕對控制的是 Web 頁面的結構。您的標記不可更改,用戶就不能亂弄;他們的瀏覽器只能從您的 Web 服務器檢索標記並顯示它(雖然樣式更符合用戶的品味而不是您自己的品味)。但頁面組織,不管是在該段落內還是在其他分區,都只由您單獨決定。要是想實際更改您的頁面(這是大多數 Ajax 應用程序所關註的),您操作的是頁面的結構。盡管很容易更改一段文本的顏色,但在現有頁面上添加文本或整個區段要難得多。不管用戶如何設計該區段的樣式,都是由您控制頁面本身的組織。


標記做什麼


一旦意識到您的標記是真正與組織相關的,您就會對它另眼相看了。不會認為 h1 導致文本是大字號、黑色、粗體的,而會認為 h1 是標題。用戶如何看待這個問題以及他們是使用您的 CSS、他們自己的 CSS 還是這兩者的組合,這是次要的考慮事項。相反,要意識到只有標記才能提供這種級別的組織;p 指明文本在段落內,img 表示圖像,div 將頁面分成區段,等等。


還應該清楚,樣式和行為(事件處理程序和 JavaScript)是在事後 應用於該組織的。標記就緒以後才能對其進行操作或設計樣式。所以,正如您可以將 CSS 保存在 HTML 的外部文件中一樣,標記的組織與其樣式、格式和行為是分離的。雖然您肯定可以用 JavaScript 更改元素或文本的樣式,但實際更改您的標記所布置的組織卻更加有趣。


只要牢記您的標記只為您的頁面提供組織、框架,您就能立於不敗之地。再前進一小步,您就會明白瀏覽器是如何接受所有的文本組織並將其轉變為超級有趣的一些東西的,即一組對象,其中每個對象都可被更改、添加或刪除。


文本標記的優點


在討論 Web 瀏覽器之前,值得考慮一下為什麼純文本絕對 是存儲 HTML 的最佳選擇(有關詳細信息,請參閱 有關標記的一些其他想法)。不考慮優缺點,只是回憶一下在每次查看頁面時 HTML 是通過網絡發送到 Web 瀏覽器的(為了簡潔,不考慮高速緩存等)。真是再沒有比傳遞文本再有效的方法了。二進制對象、頁面圖形表示、重新組織的標記塊等等,所有這一切都比純文本文件通過網絡傳遞要更困難。


此外,瀏覽器也為此增光添彩。今天的瀏覽器允許用戶更改文本大小、按比例伸縮圖像、下載頁面的 CSS 或 JavaScript(大多數情況),甚至更多,這完全排除了將任何類型的頁面圖形表示發送到瀏覽器上。但是,瀏覽器需要原 HTML,這樣它才能在瀏覽器中對頁面應用任何處理,而不是信任瀏覽器去處理該任務。同樣地,將 CSS 從 JavaScript 分離和將 CSS 從 HTML 標記分離要求一種容易分離的格式。文本文件又一次成為該任務的最好方法。


最後但同樣重要的一點是,記住,新標準(比如 HTML 4.01 與 XHTML 1.0 和 1.1)承諾將內容(頁面中的數據)與表示和樣式(通常由 CSS 應用)分離。如果程序員要將 HTML 與 CSS 分離,然後強制瀏覽器檢索粘結頁面各部分的一些頁面表示,這會失去這些標準的多數優點。保持這些部分到達瀏覽器時都一直分離使得瀏覽器在從服務器獲取 HTML 時有了前所未有的靈活性。


關於標記的其他想法


純文本編輯:是對是錯?

純文本是存儲標記的理想選擇,但是不適合編輯 標記。大行其道的是使用 IDE,比如 Macromedia DreamWeaver 或更強勢點的 Microsoft? FrontPage?,來操作 Web 頁面標記。這些環境通常提供快捷方式和幫助來創建 Web 頁面,尤其是在使用 CSS 和 JavaScript 時,二者都來自實際頁面標記以外的文件。許多人仍偏愛好用古老的記事本或 vi(我承認我也是其中一員),這並不要緊。不管怎樣,最終結果都是充滿標記的文本文件。


網絡上的文本:好東西


已經說過,文本是文檔的最好媒體,比如 HTML 或 CSS,在網絡上被千百次地傳輸。當我說瀏覽器表示文本很難時,是特指將文本轉換為用戶查看的可視圖形頁面。這與瀏覽器實際上如何從 Web 瀏覽器檢索頁面沒有關系;在這種情況下,文本仍然是最佳選擇。


文本標記的缺點


正如文本標記對於設計人員和頁面創建者具有驚人的優點之外,它對於瀏覽器也具有相當出奇的缺點。具體來說,瀏覽器很難直接將文本標記可視地表示給用戶(詳細信息請參閱 有關標記的一些其他想法)。考慮下列常見的瀏覽器任務:


·基於元素類型、類、ID 及其在 HTML 文檔中的位置,將 CSS 樣式(通常來自外部文件中的多個樣式表)應用於標記。

·基於 JavaScript 代碼(通常位於外部文件)將樣式和格式應用於 HTML 文檔的不同部分。

·基於 JavaScript 代碼更改表單字段的值。

·基於 JavaScript 代碼,支持可視效果,比如圖像翻轉和圖像交換。


復雜性並不在於編碼這些任務;其中每件事都是相當容易的。復雜性來自實際實現請求動作的瀏覽器。如果標記存儲為文本,比如,想要在 center-text 類的 p 元素中輸入文本 (text-align: center),如何實現呢?


·將內聯樣式添加到文本嗎?

·將樣式應用到瀏覽器中的 HTML 文本,並只保持內容居中或不居中?

·應用無樣式的 HTML,然後事後應用格式?


這些非常困難的問題是如今很少有人編寫瀏覽器的原因。(編寫瀏覽器的人應該接受最由衷的感謝)


無疑,純文本不是存儲瀏覽器 HTML 的好辦法,盡管文本是獲取頁面標記最好的解決方案。如果加上 JavaScript 更改 頁面結構的能力,事情就變得有些微妙了。瀏覽器應該將修改過的結構重新寫入磁盤嗎?如何才能保持文檔的最新版本呢?


無疑,文本不是答案。它難以修改,為其應用樣式和行為很困難,與今天 Web 頁面的動態本質在根本上相去甚遠。


求助於樹視圖


這個問題的答案(至少是由當今 Web 瀏覽器選擇的答案)是使用樹結構來表示 HTML。參見 清單 1,這是一個表示為本文標記的相當簡單又無聊的 HTML 頁面。


清單 1. 文本標記中的簡單 HTML 頁面


<html>

<head>

  <title>Trees, trees, everywhere</title>

</head>

<body>

  <h1>Trees, trees, everywhere</h1>

  <p>Welcome to a <em>really</em> boring page.</p>

  <div>

    Come again soon.

    <img src="come-again.gif" />

  </div>

</body>

</html>

瀏覽器接受該頁面並將之轉換為樹形結構,如圖 1 所示。




為了保持本文的進度,我做了少許簡化。DOM 或 XML 方面的專家會意識到空白對於文檔文本在 Web 瀏覽器樹結構中表示和分解方式的影響。膚淺的了解只會使事情變得模棱兩可,所以如果想弄清空白的影響,那最好不過了;如果不想的話,那可以繼續讀下去,不要考慮它。當它成為問題時,那時您就會明白您需要的一切。


除了實際的樹背景之外,可能會首先註意到樹中的一切是以最外層的 HTML 包含元素,即 html 元素開始的。使用樹的比喻,這叫做根元素。所以即使這是樹的底層,當您查看並分析樹的時候,我也通常以此開始。如果它確實奏效,您可以將整個樹顛倒一下,但這確實有些拓展了樹的比喻。


從根流出的線表示不同標記部分之間的關系。head 和 body 元素是 html 根元素的孩子;title 是 head 的孩子,而文本 “Trees, trees, everywhere” 是 title 的孩子。整個樹就這樣組織下去,直到瀏覽器獲得與 圖 1 類似的結構。


一些附加術語


為了沿用樹的比喻,head 和 body 被叫做 html 的分支(branches)。叫分支是因為它們有自己的孩子。當到達樹的末端時,您將進入主要的文本,比如 “Trees, trees, everywhere” 和 “really”;這些通常稱為葉子,因為它們沒有自己的孩子。您不需要記住所有這些術語,當您試圖弄清楚特定術語的意思時,只要想像一下樹結構就容易多了。


對象的值


既然了解了一些基本的術語,現在應該關註一下其中包含元素名稱和文本的小矩形了(圖 1)。每個矩形是一個對象;瀏覽器在其中解決一些文本問題。通過使用對象來表示 HTML 文檔的每一部分,可以很容易地更改組織、應用樣式、允許 JavaScript 訪問文檔,等等。


對象類型和屬性


標記的每個可能類型都有自己的對象類型。例如,HTML 中的元素用 Element 對象類型表示。文檔中的文本用 Text 類型表示,屬性用 Attribute 類型表示,以此類推。


所以 Web 瀏覽器不僅可以使用對象模型來表示文檔(從而避免了處理靜態文本),還可以用對象類型立即辨別出某事物是什麼。HTML 文檔被解析並轉換為對象集合,如 圖 1 所示,然後尖括號和轉義序列(例如,使用 < 表示 <,使用 > 表示 >)等事物不再是問題了。這就使得瀏覽器的工作(至少在解析輸入 HTML 之後)變得更容易。弄清某事物究竟是元素還是屬性並確定如何處理該類型的對象,這些操作都十分簡單了。


通過使用對象,Web 瀏覽器可以更改這些對象的屬性。例如,每個元素對象具有一個父元素和一系列子元素。所以添加新的子元素或文本只需要向元素的子元素列表中添加一個新的子元素。這些對象還具有 style 屬性,所以快速更改元素或文本段的樣式非常簡單。例如,要使用 JavaScript 更改 div 的高度,如下所示:


someDiv.style.height = "300px";

換句話說,Web 瀏覽器使用對象屬性可以非常容易地更改樹的外觀和結構。將之比作瀏覽器在內部將頁面表示為文本時必須進行的復雜事情,每次更改屬性或結構都需要瀏覽器重新編寫靜態文件、重新解析並在屏幕上重新顯示。有了對象,所有這一切都解決了。


現在,花點時間展開一些 HTML 文檔並用樹將其勾畫出來。盡管這看起來是個不尋常的請求(尤其是在包含極少代碼的這樣一篇文章中),如果您希望能夠操縱這些樹,那麼需要熟悉它們的結構。


在這個過程中,可能會發現一些古怪的事情。比如,考慮下列情況:


·屬性發生了什麼?

·分解為元素(比如 em 和 b)的文本呢?

·結構不正確(比如當缺少結束 p 標記時)的 HTML 呢?


一旦熟悉這些問題之後,就能更好地理解下面幾節了。


嚴格有時是好事


如果嘗試剛提到的練習 I,您可能會發現標記的樹視圖中存在一些潛在問題(如果不練習的話,那就聽我說吧!)。事實上,在 清單 1 和 圖 1 中就會發現一些問題,首先看 p 元素是如何分解的。如果您問通常的 Web 開發人員 “p 元素的文本內容是什麼”,最常見的答案將是 “Welcome to a really boring Web page.”。如果將之與圖 1 做比較,將會發現這個答案(雖然合乎邏輯)是根本不正確的。


實際上,p 元素具有三個 不同的子對象,其中沒有一個包含完整的 “Welcome to a really boring Web page.” 文本。您會發現文本的一部分,比如 “Welcome to a ” 和 “ boring Web page”,但不是全部。為了理解這一點,記住標記中的任何內容都必須轉換為某種類型的對象。


此外,順序無關緊要!如果瀏覽器顯示正確的對象,但顯示順序與您在 HTML 中提供的順序不同,那麼您能想像出用戶將如何響應 Web 瀏覽器嗎?段落夾在頁面標題和文章標題中間,而這不是您自己組織文檔時的樣式呢?很顯然,瀏覽器必須保持元素和文本的順序。 


在本例中,p 元素有三個不同部分: 


·em 元素之前的文本

·em 元素本身

·em 元素之後的文本


如果將該順序打亂,可能會把重點放在文本的錯誤部分。為了保持一切正常,p 元素有三個子對象,其順序是在 清單 1 的 HTML 中顯示的順序。而且,重點文本 “really” 不是p 的子元素;而是 p 的子元素 em 的子元素。


理解這一概念非常重要。盡管 “really” 文本將可能與其他 p 元素文本一起顯示,但它仍是 em 元素的直接子元素。它可以具有與其他 p 文本不同的格式,而且可以獨立於其他文本到處移動。


要將之牢記在心,試著用圖表示清單 2 和 3 中的 HTML,確保文本具有正確的父元素(而不管文本最終會如何顯示在屏幕上)。


清單 2. 帶有巧妙元素嵌套的標記


<html>

<head>

  <title>This is a little tricky</title>

</head>

<body>

  <h1>Pay <u>close</u> attention, OK?</h1>

  <div>

   <p>This p really isn't <em>necessary</em>, but it makes the 

      <span id="bold-text">structure <i>and</i> the organization</span>

      of the page easier to keep up with.</p>

  </div>

</body>

</html>


清單 3. 更巧妙的元素嵌套


<html>

<head>

  <title>Trickier nesting, still</title>

</head>

<body>

  <div id="main-body">

   <div id="contents">

    <table> 

     <tr><th>Steps</th><th>Process</th></tr>

     <tr><td>1</td><td>Figure out the <em>root element</em>.</td></tr>

     <tr><td>2</td><td>Deal with the <span id="code">head</span> first,

         as it's usually easy.</td></tr>

     <tr><td>3</td><td>Work through the <span id="code">body</span>.

         Just <em>take your time</em>.</td></tr>

    </table>

   </div>

   <div id="closing">

    This link is <em>not</em> active, but if it were, the answers

    to this <a href="answers.html"><img src="exercise.gif" /></a> would

    be there. But <em>do the exercise anyway!</em>

   </div>

  </div>

</body>

</html>

在本文末的 GIF 文件 圖 2 中的 tricky-solution.gif 和 圖 3 中的 trickier-solution.gif 中將會找到這些練習的答案。不要偷看,先花些時間自動解答一下。這樣能幫助您理解組織樹時應用的規則有多麼嚴格,並真正幫助您掌握 HTML 及其樹結構。


屬性呢?


當您試圖弄清楚如何處理屬性時,是否遇到一些問題呢?前已提及,屬性確實具有自己的對象類型,但屬性確實不是顯示它的元素的子元素,嵌套元素和文本不在同一屬性 “級別”,您將註意到,清單 2 和 3 中練習的答案沒有顯示屬性。


屬性事實上存儲在瀏覽器使用的對象模型中,但它們有一些特殊情況。每個元素都有可用屬性的列表,且與子對象列表是分離的。所以 div 元素可能有一個包含屬性 “id” 和另一個屬性 “class” 的列表。


記住,元素的屬性必須具有惟一的名稱,也就是說,一個元素不能有兩個 “id” 或兩個 “class” 屬性。這使得列表易於維護和訪問。在下一篇文章將會看到,您可以簡單調用諸如 getAttribute("id") 的方法來按名稱獲取屬性的值。還可以用相似的方法調用來添加屬性或設置(重置)現有屬性的值。


值得指出的是,屬性名的惟一性使得該列表不同於子對象列表。p 元素可以有多個 em 元素,所以子對象列表可以包含多個重復項。盡管子項列表和屬性列表的操作方式相似,但一個可以包含重復項(對象的子項),而一個不能(元素對象的屬性)。最後,只有元素具有屬性,所以文本對象沒有用於存儲屬性的附加列表。


淩亂的 HTML


在繼續之前,談到瀏覽器如何將標記轉換為樹表示,還有一個主題值得探討,即瀏覽器如何處理不是格式良好的標記。格式良好 是 XML 廣泛使用的一個術語,有兩個基本意思:


·每個開始標記都有一個與之匹配的結束標記。所以每個 <p> 在文檔中與 </p> 匹配,每個 <div> 與 </div> 匹配,等等。

·最裏面的開始標記與最裏面的結束標記相匹配,然後次裏面的開始標記與次裏面的結束標記相匹配,依此類推。所以 <b><i>bold and italics</b></i> 是不合法的,因為最裏面的開始標記 <i> 與最裏面的結束標記 <b> 匹配不當。要使之格式良好,要麼 切換開始標記順序,要麼 切換結束標記順序。(如果兩者都切換,則仍會出現問題)。

深入研究這兩條規則。這兩條規則不僅簡化了文檔的組織,還消除了不定性。是否應先應用粗體後應用斜體?或恰恰相反?如果覺得這種順序和不定性不是大問題,那麼請記住,CSS 允許規則覆蓋其他規則,所以,例如,如果 b 元素中文本的字體不同於 i 元素中的字體,則格式的應用順序將變得非常重要。因此,HTML 的格式良好性有著舉足輕重的作用。


如果瀏覽器收到了不是格式良好的文檔,它只會盡力而為。得到的樹結構在最好情況下將是作者希望的原始頁面的近似,最壞情況下將面目全非。如果您曾將頁面加載到瀏覽器中後看到完全出乎意料的結果,您可能在看到瀏覽器結果時會猜想您的結構應該如何,並沮喪地繼續工作。當然,搞定這個問題相當簡單:確保文檔是格式良好的!如果不清楚如何編寫標準化的 HTML,請咨詢 參考資料 獲得幫助。


DOM 簡介


到目前為止,您已經知道瀏覽器將 Web 頁面轉換為對象表示,可能您甚至會猜想,對象表示是 DOM 樹。DOM 表示 Document Object Model,是一個規範,可從 World Wide Web Consortium (W3C) 獲得(您可以參閱 參考資料 中的一些 DOM 相關鏈接)。


但更重要的是,DOM 定義了對象的類型和屬性,從而允許瀏覽器表示標記。(本系列下一篇文章將專門講述在 JavaScript 和 Ajax 代碼中使用 DOM 的規範。)


文檔對象


首先,需要訪問對象模型本身。這非常容易;要在運行於 Web 頁面上的任何 JavaScript 代碼中使用內置 document 變量,可以編寫如下代碼:


var domTree = document;

當然,該代碼本身沒什麼用,但它演示了每個 Web 瀏覽器使得 document 對象可用於 JavaScript 代碼,並演示了對象表示標記的完整樹(圖 1)。


每項都是一個節點


顯然,document 對象很重要,但這只是開始。在進一步深入之前,需要學習另一個術語:節點。您已經知道標記的每個部分都由一個對象表示,但它不只是一個任意的對象,它是特定類型的對象,一個 DOM 節點。更特定的類型,比如文本、元素和屬性,都繼承自這個基本的節點類型。所以可以有文本節點、元素節點和屬性節點。


如果已經有很多 JavaScript 編程經驗,那您可能已經在使用 DOM 代碼了。如果到目前為止您一直在跟蹤本 Ajax 系列,那麼現在您一定 使用 DOM 代碼有一段時間了。例如,代碼行 var number = document.getElementById("phone").value; 使用 DOM 查找特定元素,然後檢索該元素的值(在本例中是一個表單字段)。所以即使您沒有意識到這一點,但您每次將 document 鍵入 JavaScript 代碼時都會使用 DOM。


詳細解釋已經學過的術語,DOM 樹是對象的樹,但更具體地說,它是節點 對象的樹。在 Ajax 應用程序中或任何其他 JavaScript 中,可以使用這些節點產生下列效果,比如移除元素及其內容,突出顯示特定文本,或添加新圖像元素。因為都發生在客戶端(運行在 Web 瀏覽器中的代碼),所以這些效果立即發生,而不與服務器通信。最終結果通常是應用程序感覺起來響應更快,因為當請求轉向服務器時以及解釋響應時,Web 頁面上的內容更改不會出現長時間的停頓。


在多數編程語言中,需要學習每種節點類型的實際對象名稱,學習可用的屬性,並弄清楚類型和強制轉換;但在 JavaScript 中這都不是必需的。您可以只創建一個變量,並為它分配您希望的對象(正如您已經看到的):


var domTree = document;

var phoneNumberElement = document.getElementById("phone");

var phoneNumber = phoneNumberElement.value;

沒有類型,JavaScript 根據需要創建變量並為其分配正確的類型。結果,從 JavaScript 中使用 DOM 變得微不足道(將來有一篇文章會專門講述與 XML 相關的 DOM,那時將更加巧妙)。


結束語


在這裏,我要給您留一點懸念。顯然,這並非是對 DOM 完全詳盡的說明;事實上,本文不過是 DOM 的簡介。DOM 的內容要遠遠多於我今天介紹的這些!


本系列的下一篇文章將擴展這些觀點,並深入探討如何在 JavaScript 中使用 DOM 來更新 Web 頁面、快速更改 HTML 並為您的用戶創建更交互的體驗。在後面專門講述在 Ajax 請求中使用 XML 的文章中,我將再次返回來討論 DOM。所以要熟悉 DOM,它是 Ajax 應用程序的一個主要部分。


此時,深入了解 DOM 將十分簡單,比如詳細設計如何在 DOM 樹中移動、獲得元素和文本的值、遍歷節點列表,等等,但這可能會讓您有這種印象,即 DOM 是關於代碼的,而事實上並非如此。


在閱讀下一篇文章之前,試著思考一下樹結構並用一些您自己的 HTML 實踐一下,以查看 Web 瀏覽器是如何將 HTML 轉換為標記的樹視圖的。此外,思考一下 DOM 樹的組織,並用本文介紹的特殊情況實踐一下:屬性、有元素混合在其中的文本、沒有 文本內容的元素(比如 img 元素)。


如果紮實掌握了這些概念,然後學習了 JavaScript 和 DOM 的語法(下一篇文章),則會使得響應更為容易。


而且不要忘了,這裏有清單 2 和 3 的答案,其中還包含了示例代碼!


本文来自电脑技术网www.it892.com),转载本文请注明来源.
本文链接:http://www.it892.com/content/web/ajax/0H310I542018.html

推荐阅读
无觅相关文章插件,快速提升流量