日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

Ajax 完整教程 (转)

發布時間:2023/11/27 生活经验 57 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Ajax 完整教程 (转) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Ajax 完整教程

第 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 的答案,其中還包含了示例代碼!

圖 2. 清單 2 的答案



圖 3. 清單 3 的答案


轉載于:https://www.cnblogs.com/qilinge/p/5267367.html

總結

以上是生活随笔為你收集整理的Ajax 完整教程 (转)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

日韩在线视频二区 | 久久免费视频网站 | 久久高清片 | 成人在线免费视频观看 | 国产欧美精品在线观看 | 349k.cc看片app| 在线电影 一区 | 久久99久久99精品免费看小说 | 91精品免费在线视频 | 日韩一区二区免费在线观看 | 黄在线免费看 | 日本久久精品 | 色老板在线视频 | 91伊人久久大香线蕉蜜芽人口 | 超碰.com| 天天色天天草天天射 | 成人黄色免费观看 | 91在线麻豆 | 国产人成在线视频 | 亚洲日本一区二区在线 | 亚洲精品456在线播放第一页 | 国产精品视频免费在线观看 | 色姑娘综合天天 | 蜜臀av夜夜澡人人爽人人桃色 | av在线免费播放 | 99精品在线免费在线观看 | 欧美性生交大片免网 | 午夜日b视频 | 天天综合色天天综合 | 国产在线不卡视频 | 国产一级精品视频 | 狠狠色婷婷丁香六月 | 亚洲年轻女教师毛茸茸 | 亚洲精品国精品久久99热一 | 99久久精品国产网站 | 日韩精品首页 | 欧美黑人xxxx猛性大交 | 午夜精品一区二区三区在线视频 | 麻豆免费视频观看 | 又黄又刺激的视频 | 欧美激情视频一区二区三区 | 制服丝袜亚洲 | 日韩精品欧美专区 | 色一级片 | 在线蜜桃视频 | 99久久精品免费看国产免费软件 | 欧美精品久久久久 | 色婷丁香 | 亚洲丁香久久久 | 色在线视频网 | 超碰在线91 | 久久夜色精品国产欧美一区麻豆 | 久久97超碰 | 91精品视屏 | 黄色网www | 日日操狠狠干 | 在线观看香蕉视频 | 免费午夜网站 | 开心激情五月婷婷 | 国产一卡久久电影永久 | 国产亚洲综合精品 | 奇米四色影狠狠爱7777 | 国产福利精品在线观看 | 超碰人人草 | 五月天激情视频在线观看 | 欧美在一区 | www黄色软件 | 黄色网中文字幕 | 欧美大片aaa | 免费在线播放黄色 | 日韩三级精品 | 亚洲精品在线观看免费 | 国产精品99免视看9 国产精品毛片一区视频 | 欧美与欧洲交xxxx免费观看 | 国产精品v a免费视频 | 国产亚洲在线视频 | 九九欧美视频 | 久久网页| 色妞色视频一区二区三区四区 | 欧美激情第一区 | 日韩精品免费一区 | 久久精品免费观看 | 天天干天天射天天插 | 香蕉视频免费看 | 狠狠干成人综合网 | 中文字幕色在线视频 | 久久一区二区三区日韩 | 国产精品ssss在线亚洲 | 亚洲成人精品在线 | 蜜桃视频成人在线观看 | 日韩精品久久久久久久电影竹菊 | 天堂在线视频中文网 | 98涩涩国产露脸精品国产网 | 夜色资源站wwwcom | 国产精品久久久久久久久久99 | 国产日韩精品在线观看 | 国内精品久久久精品电影院 | 久久免费福利视频 | 国产精品美女999 | 在线观看黄 | 麻豆一精品传二传媒短视频 | 亚洲在线精品视频 | 日韩精品观看 | 精品国产一区二区三区四区vr | 超碰97国产在线 | 国产一线在线 | 日韩精品一区二区久久 | 日韩欧美网站 | 亚洲成人欧美 | 免费看毛片网站 | 国产涩涩在线观看 | 午夜av激情| 一区二区不卡视频在线观看 | 99久久婷婷国产 | 中文字幕在线免费 | 日韩欧美视频在线播放 | 在线成人一区二区 | 国产中文字幕大全 | 一区二区三区精品久久久 | 午夜黄色一级片 | 久久久久久久久久电影 | 久久精品亚洲精品国产欧美 | 中文字幕精品一区久久久久 | 色九九影院 | 韩国av一区二区三区在线观看 | 九九久久婷婷 | 99久久精品国产观看 | 国产精品久久久久久一区二区 | 欧美 日韩 久久 | 国产精品国产三级国产不产一地 | 日韩欧美黄色网址 | 黄色三级av| 日韩一二区在线观看 | 日韩黄色软件 | 手机在线永久免费观看av片 | 国内精品在线看 | 91aaa在线观看 | 欧美日韩破处 | 婷婷六月天综合 | 综合av在线 | 久香蕉| 久久久香蕉视频 | 中文字幕在线观看视频免费 | 中文字幕欧美日韩va免费视频 | 在线成人免费电影 | 天天射天天干天天操 | 久艹在线观看视频 | 91激情小视频| 国产日韩欧美网站 | 国产视频资源在线观看 | 一区二区三区免费在线观看视频 | 日韩免费电影一区二区 | 在线 视频 一区二区 | 免费在线观看av的网站 | 91成品人影院 | 成人国产精品久久久久久亚洲 | 日本 在线 视频 中文 有码 | 五月天色网站 | 欧美高清视频不卡网 | 欧美激情另类 | 99一区二区三区 | 狠狠色丁香久久婷婷综合_中 | 日韩美女av在线 | 在线免费视 | 91精品国产高清自在线观看 | 国产乱视频| 久久综合国产伦精品免费 | 免费观看全黄做爰大片国产 | 久久电影网站中文字幕 | 99视频精品全部免费 在线 | 国产精品麻豆一区二区三区 | 欧美日韩精品免费观看视频 | 天堂av免费在线 | 日本精品视频一区二区 | 国产网站在线免费观看 | 欧美日韩成人一区 | 国产丝袜美腿在线 | 久久精品视频国产 | 在线看av网址 | 天天综合天天做 | 日本一区二区免费在线观看 | 精品一区二区6 | 日韩资源在线 | 最近免费中文字幕大全高清10 | 亚洲第一久久久 | 欧美亚洲国产一卡 | 日日夜夜噜噜噜 | 国产小视频福利在线 | 啪啪凸凸| 一区二三国产 | 日韩av成人在线 | 亚洲三级网站 | 激情av在线播放 | 欧美整片sss| 亚洲一区动漫 | 在线观看亚洲a | 亚洲成av人片在线观看香蕉 | 又湿又紧又大又爽a视频国产 | 欧美日韩免费观看一区二区三区 | 国产精品青草综合久久久久99 | 国产区精品视频 | 五月婷婷av | 激情欧美一区二区三区免费看 | 91成年人视频 | 91成人久久 | 欧美性生活久久 | 亚洲视频在线免费看 | 久草视频首页 | 天天插天天干 | 国产黄色免费在线观看 | 高清不卡一区二区三区 | bbbb操bbbb | 亚洲国产成人高清精品 | 美女视频黄是免费的 | 99久久99视频只有精品 | 日韩欧美区 | 久久久久久久久亚洲精品 | 九九综合在线 | 精品国产一区二区三区蜜臀 | 日韩欧美专区 | 日批视频在线播放 | 久草视频在线播放 | 日韩系列 | 国产精品18久久久久vr手机版特色 | 综合色天天| 九九视频在线观看视频6 | 欧美在线观看视频一区二区三区 | 精品美女久久 | 丁香花在线观看免费完整版视频 | 91久久精品一区二区二区 | 麻豆一二 | 视频国产在线观看18 | 国产尤物视频在线 | 国产一区二区三区高清播放 | 六月色婷婷| 日本特黄一级 | 69av免费视频 | 久久草草热国产精品直播 | 国产精品美女久久久 | 91插插插网站 | 日本三级久久 | 在线观看免费视频 | 日日干夜夜骑 | 天天爽夜夜爽人人爽曰av | www久久久| 香蕉视频在线观看免费 | 中文字幕在线播放视频 | 成人av在线播放网站 | 超碰av在线播放 | 国产一在线精品一区在线观看 | 91一区二区三区在线观看 | 黄色小说免费观看 | 69av久久| 韩国av免费| 人人狠狠综合久久亚洲婷 | 综合五月婷婷 | 国产精品丝袜在线 | 国产福利一区二区三区在线观看 | 久久久美女 | 国产自产高清不卡 | 日韩丝袜| 欧美男男激情videos | 亚洲专区一二三 | 91九色视频在线观看 | 久久国语 | 日本高清中文字幕有码在线 | 美女网站视频免费黄 | 国产精品精 | 亚洲精品毛片一级91精品 | 国产99久久久精品视频 | 91香蕉视频720p| 欧美日韩天堂 | 欧美最爽乱淫视频播放 | 九九九视频精品 | 91精品国产欧美一区二区成人 | 国产亚洲成人精品 | 99久久精品久久久久久动态片 | 综合色婷婷 | 亚洲一区视频免费观看 | 中文字幕免费一区 | 丁香免费视频 | 亚洲精品午夜视频 | 日韩免费三区 | 特级西西444www大精品视频免费看 | 国产精品久久久久毛片大屁完整版 | av观看免费在线 | 久久精品在线视频 | 中文在线亚洲 | 一级性av | 在线观看精品一区 | 人人爽人人看 | 就操操久久 | 91精品久久久久久久久久入口 | 国产精品亚洲片在线播放 | bbbbb女女女女女bbbbb国产 | 国产中文字幕大全 | 日韩综合精品 | 夜夜躁狠狠躁日日躁视频黑人 | 日韩精品一卡 | 玖玖999 | 98超碰在线观看 | 91免费网址 | 日韩一区二区三区免费电影 | 国产高清视频免费观看 | 精品久久久久久久久久 | 亚洲狠狠操 | 久久免费片 | 激情喷水 | av在线小说 | 日韩四虎 | 久久成人国产精品 | 最新av电影网站 | 亚洲成色777777在线观看影院 | 久草 | 欧美一级免费黄色片 | 日本成址在线观看 | 中文字幕色在线 | 亚洲欧美激情精品一区二区 | 韩国一区在线 | 操操操av | 天天色天天操综合网 | 五月婷婷婷婷婷 | 91在线精品视频 | 久久精品视频3 | 国产一区欧美在线 | 亚洲精品字幕在线 | 在线观看中文 | 久久久人 | 日本中文字幕在线观看 | 久久午夜电影院 | 国产午夜三级一区二区三桃花影视 | 免费av黄色 | 天天干,天天操,天天射 | 成人黄色免费在线观看 | 亚洲无吗天堂 | 国产v视频 | 午夜精品一区二区三区免费 | 日韩视频区| 99热精品国产| 91九色精品国产 | 国产视频色 | 国产在线一区二区三区播放 | 久久精品亚洲国产 | 亚洲女同ⅹxx女同tv | 狠狠狠色丁香综合久久天下网 | 91手机视频在线 | 在线婷婷 | 最近中文字幕视频网 | 中文字幕一区二区三区久久蜜桃 | 91视频88av | 美女一二三区 | 国产亚洲精品久久久久久无几年桃 | 我爱av激情网 | 天天天天天天干 | 国产精品第一视频 | 中文字幕免费高清av | 日韩精品三区四区 | 婷婷色 亚洲 | 日韩av一卡二卡三卡 | 久久精品视频一 | 国产精品美女免费 | 国产成人久久 | 播五月综合 | 免费美女久久99 | 久久久久国产精品午夜一区 | 日韩激情精品 | 中文字幕在线观看免费观看 | 在线视频日韩一区 | 午夜久操 | 一级黄色在线视频 | 狠狠躁夜夜躁人人爽超碰97香蕉 | 免费av免费观看 | 一区二区三区 亚洲 | 国产亚洲精品美女久久 | 精品uu | 国产在线精品一区二区三区 | 午夜在线资源 | 亚洲精品视频网站在线观看 | 日本aaaa级毛片在线看 | 亚洲激情五月 | 久久 精品一区 | 最新av网站在线观看 | 亚洲国产资源 | 日韩精品免费在线播放 | 99视频在线观看一区三区 | 久久精品99久久 | 少妇精品久久久一区二区免费 | 日韩免费看片 | 日韩精品最新在线观看 | 免费看片亚洲 | 手机看片中文字幕 | 波多野结衣在线播放一区 | 免费看的黄色小视频 | 国产精品毛片一区二区 | 九九视频免费观看视频精品 | 久久老司机精品视频 | 久久久久国产成人免费精品免费 | 亚洲综合欧美激情 | 免费久久99精品国产婷婷六月 | 久久久久久久久久久免费视频 | www.狠狠插.com | 特黄特色特刺激视频免费播放 | 在线国产一区二区三区 | 亚洲欧美国产精品18p | 97碰在线 | 亚洲最新av | 久久久久一区二区三区四区 | 免费色婷婷 | 99c视频高清免费观看 | 成人在线观看影院 | 成人av午夜 | 97av在线视频 | av千婊在线免费观看 | 黄色三级在线观看 | 狠狠躁夜夜躁人人爽超碰97香蕉 | 日韩av影视在线 | 视频91| 免费黄色网止 | 日韩高清成人 | 亚洲精选国产 | 99精品福利视频 | 黄色片网站av | 国产又粗又猛又色又黄视频 | www国产亚洲精品久久麻豆 | 国产在线精品区 | 中文字幕精品一区久久久久 | 97超碰在线人人 | 欧美a级在线免费观看 | 亚洲成a人片在线观看中文 中文字幕在线视频第一页 狠狠色丁香婷婷综合 | 久久久久久久免费 | 成年人三级网站 | 久久综合9988久久爱 | 色偷偷男人的天堂av | 蜜桃视频精品 | 国产精品毛片久久久久久 | 日韩av电影免费在线观看 | 91在线看视频免费 | 成人免费视频网 | 激情影音 | 免费视频久久久久 | 亚洲精品九九 | 伊在线视频 | 国产亚洲精品久久 | 天天操天天吃 | 男女激情麻豆 | 天天插天天干天天操 | 日韩毛片一区 | 久久综合久久综合这里只有精品 | 日韩免费视频线观看 | 国产精品理论片 | 国产在线播放一区二区三区 | 久久久久高清 | 婷婷综合成人 | 亚洲欧美成人综合 | 国产理论在线 | 91高清免费看 | 欧美日韩免费在线视频 | 精品96久久久久久中文字幕无 | 少妇啪啪av入口 | 国产又粗又猛又黄又爽 | av在线永久免费观看 | 日日日日日| 天天色天天射天天干 | av黄色免费在线观看 | 亚洲精品午夜aaa久久久 | 在线 精品 国产 | 美女视频一区二区 | 亚洲美女久久 | 中文av一区二区 | 欧美日韩一区二区三区视频 | 91av亚洲 | 亚洲精品天天 | 伊人狠狠操 | 特黄色大片 | 69成人在线| 99精品免费久久久久久久久日本 | 亚洲天堂精品 | www色,com| 亚洲精品麻豆 | 99 视频 高清| 久久久视频在线 | 毛片久久久 | 极品美女被弄高潮视频网站 | 99精品视频免费观看视频 | 久久免费视频99 | 国产美女被啪进深处喷白浆视频 | 亚洲一区二区三区在线看 | 亚洲无吗天堂 | 日韩视频一二三区 | 免费观看一级特黄欧美大片 | 国产h在线播放 | 999久久久久久 | 911久久香蕉国产线看观看 | 玖玖视频国产 | 国产黄色片在线 | 99精品视频精品精品视频 | 西西444www大胆高清视频 | 中文字幕中文字幕在线中文字幕三区 | 欧美日韩不卡在线 | 国产剧情一区 | 精品国产午夜 | 久久精品视频播放 | 伊人国产视频 | 五月激情av | 91精品在线免费观看视频 | 精品在线播放视频 | 国产婷婷在线观看 | 久久精品这里精品 | 91在线播放国产 | 亚洲va欧美| 亚洲欧美精品一区 | 日韩在线电影一区 | 黄色av三级在线 | 黄色一级影院 | 成人一区二区在线观看 | 99 视频 高清 | 国产精品video | 婷婷深爱 | 精品99在线 | 亚洲欧美少妇 | 日韩在线免费视频 | 国产无遮挡又黄又爽在线观看 | 久久av影院 | 中文字幕在线观看完整版电影 | 免费精品在线视频 | 在线观看黄网站 | 久久午夜鲁丝片 | 久久国产精品99久久久久久老狼 | 亚洲黄色小说网址 | 久久影院一区 | 免费视频久久久久 | 黄p在线播放 | 伊人狠狠干 | 婷婷精品国产欧美精品亚洲人人爽 | 色的网站在线观看 | 五月综合激情网 | 成人av在线一区二区 | 一区二区三区不卡在线 | 亚洲视频在线视频 | 中文字幕 成人 | 国产精品99久久久久人中文网介绍 | 日日夜夜精品免费观看 | 国产高清免费视频 | 成人午夜片av在线看 | 中文在线天堂资源 | 在线观看色网 | 99久久超碰中文字幕伊人 | 99久久99久久精品 | 国产高清免费视频 | 日韩在线中文字幕视频 | 国语精品视频 | 国产亚洲精品日韩在线tv黄 | 精品视频久久 | 亚洲亚洲精品在线观看 | 日韩极品视频在线观看 | 亚洲不卡av一区二区三区 | 97视频在线| av资源在线观看 | 黄在线免费观看 | 日韩精品在线一区 | 色视频网站免费观看 | 在线观看911视频 | 国内精品福利视频 | 亚洲成人精品在线观看 | 国产美女免费观看 | 99精品国产福利在线观看免费 | 久久免费视频国产 | 亚洲免费一级电影 | 99爱在线 | 首页中文字幕 | 99久久久国产精品免费99 | 亚洲 精品在线视频 | 色久综合 | 国产三级视频在线 | 国外成人在线视频网站 | 精品国产一区二区三区久久影院 | 激情网五月婷婷 | 日韩欧美视频在线观看免费 | 丁香婷婷亚洲 | 亚洲精品高清在线 | 国产三级午夜理伦三级 | 亚洲婷婷免费 | 欧美午夜视频在线 | 亚洲欧美国产日韩在线观看 | 成人在线播放av | 91chinesexxx| 中文字幕精品三级久久久 | 永久免费的av电影 | 国产精品二区在线观看 | 丁香六月在线观看 | 久久这里只精品 | 日韩av一区二区在线影视 | 国产免费高清 | 国产精品久久久一区二区 | 在线观看91 | 天天操夜操视频 | 亚洲精品三级 | 免费看精品久久片 | 麻豆91在线| 日本精品一区二区三区在线观看 | 精品亚洲欧美无人区乱码 | 中文字幕av专区 | 狠狠躁夜夜躁人人爽视频 | 视频国产一区二区三区 | 亚洲天天| 国产字幕在线看 | 成人羞羞视频在线观看免费 | 日日夜夜精品免费视频 | 国产精品电影一区 | 国产视频91在线 | 久久一区二区三区四区 | 五月激情视频 | 免费在线观看毛片网站 | 精品久久免费 | 日韩在线播放av | 狠狠狠干 | 国产精品99久久久久久宅男 | 日韩在线视频国产 | 成人午夜精品福利免费 | 香蕉视频亚洲 | 色综合亚洲精品激情狠狠 | 日日干网| 97在线看 | 久久av伊人 | 欧美精品久久久久 | 97成人在线视频 | 91精品国产九九九久久久亚洲 | 激情深爱.com | 国产精品久久免费看 | 激情五月看片 | 久久久久久毛片 | 国产一二区视频 | 美国三级黄色大片 | 日日夜夜天天射 | 婷婷综合网 | 97视频人人| 亚洲精品免费播放 | 欧美日韩不卡一区 | 99久久精品网 | 欧美性做爰猛烈叫床潮 | 四虎影视久久久 | 在线草 | 亚洲在线网址 | 成年人网站免费在线观看 | 国产成人久久精品 | 国产传媒一区在线 | 国产日本在线观看 | 欧美色图东方 | 日韩欧美aaa | 蜜桃av综合网 | 911精品视频 | 日韩在线视频不卡 | 伊人亚洲综合网 | 久久男女视频 | 深夜免费福利在线 | 91香蕉视频好色先生 | 欧美成人精品欧美一级乱黄 | 精品视频 | av资源免费在线观看 | 欧美一级片在线 | www毛片com| 免费视频一区二区 | 日韩免费一级电影 | 日韩成人免费在线电影 | 国产精品一区二区免费看 | 人交video另类hd | 国产手机在线 | 国产福利电影网址 | 久久av中文字幕片 | 精品国产伦一区二区三区免费 | 成人在线观看网址 | 8x成人免费视频 | 日韩在线观看视频一区二区三区 | 久久这里只精品 | 日韩免费成人 | 色综合天天干 | 九九免费在线观看视频 | 日日夜夜狠狠操 | 欧美视频日韩视频 | 成人一区二区三区在线 | 在线观看91精品视频 | 亚洲国产精品小视频 | 成人香蕉视频 | 国产精品毛片一区 | 国产在线第三页 | 欧美 激情 国产 91 在线 | 97国产电影 | 91精选在线 | 中文在线字幕观看电影 | 精品一区二区在线看 | 色综合久久久久综合 | 欧美91精品久久久久国产性生爱 | 色综合久久久久久中文网 | 欧美日韩aaaa | 亚洲精品一区二区18漫画 | 天天玩天天操天天射 | 丁香五月缴情综合网 | 又粗又长又大又爽又黄少妇毛片 | 国产精品午夜久久 | 激情视频久久 | 最新国产一区二区三区 | 1024手机看片国产 | 国产涩图 | 国产日韩精品一区二区在线观看播放 | 国产色网| 亚洲干| 亚洲另类人人澡 | 久久国产精品99久久久久久老狼 | 国产精品18久久久久vr手机版特色 | 热re99久久精品国产66热 | 亚洲国产日韩欧美 | 日日夜夜人人天天 | 国产福利一区在线观看 | 国产精品高清免费在线观看 | 亚洲精品久久久蜜桃直播 | 丁香高清视频在线看看 | 天天色天天射天天干 | 色多多视频在线观看 | 一区二区三区高清 | 国产在线一区二区 | 日日日日日 | 国产精品18久久久久久久久久久久 | 久久综合爱 | 天天射天天射天天射 | 特级西西444www大精品视频免费看 | 国产视频精品在线 | 欧美夫妻生活视频 | 国产福利一区二区三区视频 | 久久美女免费视频 | 亚洲男模gay裸体gay | 网站免费黄色 | 国产午夜精品视频 | 96国产精品 | 欧美亚洲久久 | 久久国产三级 | 久久国产精品免费一区 | 天天干天天射天天爽 | 在线观看日本韩国电影 | 亚州人成在线播放 | 操操日日| 日韩视频图片 | 成人亚洲精品久久久久 | 日批网站免费观看 | 在线免费观看黄色 | 亚洲精品1区2区3区 超碰成人网 | 久久久久久久国产精品影院 | 特级西西人体444是什么意思 | 国产在线精品国自产拍影院 | aaa日本高清在线播放免费观看 | 国内成人av | 免费观看mv大片高清 | 色99视频| 人人插人人澡 | а天堂中文最新一区二区三区 | 亚洲精品中文字幕在线观看 | 午夜国产福利在线 | 国产一区黄色 | av中文字幕网 | www.xxxx变态.com| 亚洲热久久 | 夜夜躁日日躁狠狠久久88av | 免费看的毛片 | 天天干人人干 | av超碰在线 | 国产日本亚洲高清 | 欧美日韩成人 | 亚洲国产一区在线观看 | 超碰人人舔 | 伊人婷婷久久 | 亚洲最新av网址 | 中文字幕 91 | 成人国产网址 | 精品国产乱码久久久久久三级人 | 在线播放日韩av | 一级一片免费视频 | 久久亚洲私人国产精品va | 丁香综合五月 | 久操视频在线 | 亚洲影院一区 | 欧美精品一区二区三区一线天视频 | 免费在线观看一级片 | 久久草精品 | 日韩精品免费一线在线观看 | 国产精品久久一区二区三区, | 超碰在线中文字幕 | 久操久 | 日韩午夜一级片 | 女人18片毛片90分钟 | 97在线观看免费视频 | 91色网址 | 亚洲最新在线视频 | 丁香九月激情综合 | 99在线视频网站 | 99精品国产兔费观看久久99 | 天天摸天天舔天天操 | 国产精品中文字幕在线播放 | japanesefreesex中国少妇 | 美女免费黄视频网站 | 丝袜美腿一区 | 日韩av午夜在线观看 | 久久久综合九色合综国产精品 | 成人免费视频网站 | 久久久久综合网 | av免费在线观看网站 | 九九日韩| 日韩免费小视频 | 亚洲视频电影在线 | 国产成人av网址 | 91精选| 国产久草在线观看 | 亚洲国产三级在线观看 | 西西444www大胆无视频 | 在线看91| 日韩视频二区 | 91激情视频在线 | 久久免费毛片视频 | 国产精在线| 蜜臀久久99精品久久久无需会员 | 97超碰超碰久久福利超碰 | www99精品| 99热99re6国产在线播放 | 国产精品久久久精品 | 91av在线免费 | 国产成人l区 | 国产成人综合图片 | 欧美另类交人妖 | 婷婷亚洲激情 | 人人干狠狠干 | 成 人 黄 色 片 在线播放 | 国产精品嫩草69影院 | 91看片淫黄大片在线播放 | 黄色免费大片 | 国产麻豆视频在线观看 | 亚洲精品99久久久久中文字幕 | 又黄又爽又刺激视频 | 91av在线免费播放 | 久久久精品在线观看 | 天天干天天操天天做 | 亚洲视频2 | 国产手机视频 | 久久久久久久精 | 国产不卡av在线 | 色的网站在线观看 | 亚洲一区二区精品 | 久久a免费视频 | 97在线观看免费观看高清 | 97看片网 | 精品久久久影院 | 国产精品免费久久久久久久久久中文 | 日韩午夜三级 | 黄色网在线免费观看 | 在线免费观看黄 | 人人超在线公开视频 | 91在线免费公开视频 | 中文字幕免费看 | 亚洲人片在线观看 | 久久久久久亚洲精品 | 人人狠 | 久久久久成人精品 | 97成人在线观看视频 | 国产精品videossex国产高清 | 日韩精品一区二区在线视频 | 午夜精品视频一区 | 亚洲人在线视频 | 在线观看日韩视频 | 国产日产精品一区二区三区四区的观看方式 | 亚洲国产美女精品久久久久∴ | a资源在线| 国产999精品 | 国产精品自产拍 | 欧美在线a视频 | 天天操天天舔天天爽 | www99精品| 日韩精品一区二区三区三炮视频 | av一区二区三区在线观看 | 免费高清在线视频一区· | 91在线免费播放视频 | 久草视频在线免费播放 | 韩国一区在线 | 欧美成人免费在线 | 国产96在线视频 | 亚洲精品乱码久久久一二三 | 久久免费视频99 | 久久视频免费在线 | 日韩一二区在线观看 | 国产精品久久久久亚洲影视 | 香蕉在线视频播放网站 | 超碰com| 波多野结衣视频网址 | av中文天堂 | 亚洲精品免费播放 | 蜜臀久久99静品久久久久久 | 久久亚洲视频 | 亚洲美女精品区人人人人 | 日韩电影在线一区二区 | 午夜精品视频免费在线观看 | 99久久久国产精品美女 | 国产精品久久久久久久电影 | 在线观看视频色 | 69亚洲乱 | 91九色porny蝌蚪视频 | 日韩a在线看 | 99在线免费观看视频 | 精品久久久久久国产偷窥 | 特级片免费看 | wwwav视频| 久草视频免费播放 | 精品亚洲欧美一区 | 欧美国产日韩一区二区 | 国产亚洲精品福利 | 91污污视频在线观看 | 在线观看黄色国产 | 日韩试看| 91爱爱网址 | 日本一区二区三区免费看 | 欧美人zozo| 日韩精品欧美视频 | 99久久久国产精品免费99 | 久热爱 | 免费在线观看亚洲视频 | 国产男女无遮挡猛进猛出在线观看 | 亚洲综合激情 | 久久a视频| 午夜99| 在线小视频 | 99久久精品免费看 | 成人一级片免费看 | 国产小视频免费在线观看 | 成人免费一区二区三区在线观看 | 国产男女免费完整视频 | 在线观看精品 | 久久天 | 天天爱天天射天天干天天 | 欧美视频一区二 | 午夜在线资源 | 免费观看性生交 | av一级片网站 | 色婷婷影视 | 在线免费观看黄色小说 | 日韩欧美在线视频一区二区三区 | 国产香蕉视频在线观看 | 中文字幕影片免费在线观看 | 精品国产视频在线观看 | 国产淫片免费看 | 欧美激情第八页 | 久久九九国产视频 | a视频在线 | 一二三区视频在线 | 日韩va欧美va亚洲va久久 | 免费在线观看成人av | 欧美精品v国产精品 | 日韩精品在线免费观看 | 一区二区电影网 | 久艹在线播放 | 极品中文字幕 | 亚洲国产精品成人女人久久 | 色在线视频| 在线成人性视频 | 亚洲成人高清在线 | 久久午夜电影院 | 综合网在线视频 | 亚洲成色777777在线观看影院 | 中文字幕日韩伦理 | 国产在线观看免 | 久久中国精品 | 国产精品永久免费观看 | 91天天视频| 日韩视频一区二区三区在线播放免费观看 | 免费 在线 中文 日本 | 五月婷婷在线综合 | 国产九九九精品视频 | 精品99免费视频 | 激情五月婷婷激情 | 国产成人一二三 | 国产精品日韩久久久久 | 伊人久久电影网 | 亚洲码国产日韩欧美高潮在线播放 | 在线影院av | 国产精品女 | 国产aa精品 | 亚洲精品中文字幕在线 | 欧美日韩免费一区二区三区 | 久久亚洲免费视频 | 成年人看片 | 亚洲精品短视频 | 日韩av影视在线 | 亚洲毛片在线观看. | 黄色免费网站 | 日韩中文字幕视频在线观看 | 成人精品一区二区三区中文字幕 | 狠狠色丁香婷婷综合视频 | 日韩av一区二区三区在线观看 | 亚洲精品视频在线观看网站 | 国产一级不卡视频 | 狠狠操狠狠干2017 | 国产美女精品久久久 | 久艹视频在线免费观看 | 婷婷在线色 |