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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

Ajax 完整教程

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

轉(zhuǎn)載:http://www.cnblogs.com/Garden-blog/archive/2011/03/11/1981778.html

Ajax 完整教程

第 1 頁 Ajax 簡介

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

五年前,如果不知道 XML,您就是一只無人重視的丑小鴨。十八個(gè)月前,Ruby 成了關(guān)注的中心,不知道 Ruby 的程序員只能坐冷板凳了。今天,如果想跟上最新的技術(shù)時(shí)尚,那您的目標(biāo)就是 Ajax。

但是,Ajax 不僅僅 是一種時(shí)尚,它是一種構(gòu)建網(wǎng)站的強(qiáng)大方法,而且不像學(xué)習(xí)一種全新的語言那樣困難。

但在詳細(xì)探討 Ajax 是什么之前,先讓我們花幾分鐘了解 Ajax 做 什么。目前,編寫應(yīng)用程序時(shí)有兩種基本的選擇:

·桌面應(yīng)用程序?
·Web 應(yīng)用程序

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

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

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

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

還等什么呢?我們來看看 Ajax 如何將笨拙的 Web 界面轉(zhuǎn)化成能迅速響應(yīng)的 Ajax 應(yīng)用程序吧。

老技術(shù),新技巧

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

下面是 Ajax 應(yīng)用程序所用到的基本技術(shù):

·HTML 用于建立 Web 表單并確定應(yīng)用程序其他部分使用的字段。?
·JavaScript 代碼是運(yùn)行 Ajax 應(yīng)用程序的核心代碼,幫助改進(jìn)與服務(wù)器應(yīng)用程序的通信。?
·DHTML 或 Dynamic HTML,用于動(dòng)態(tài)更新表單。我們將使用 div、span 和其他動(dòng)態(tài) HTML 元素來標(biāo)記 HTML。?
·文檔對象模型 DOM 用于(通過 JavaScript 代碼)處理 HTML 結(jié)構(gòu)和(某些情況下)服務(wù)器返回的 XML。

Ajax 的定義

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

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

XMLHttpRequest 對象

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

清單 1. 創(chuàng)建新的 XMLHttpRequest 對象

<script language="javascript" type="text/javascript">
var xmlHttp = new XMLHttpRequest();
</script>
下一期文章中將進(jìn)一步討論這個(gè)對象,現(xiàn)在要知道這是處理所有服務(wù)器通信的對象。繼續(xù)閱讀之前,先停下來想一想:通過 XMLHttpRequest 對象與服務(wù)器進(jìn)行對話的是 JavaScript 技術(shù)。這不是一般的應(yīng)用程序流,這恰恰是 Ajax 的強(qiáng)大功能的來源。

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

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

然后,服務(wù)器將數(shù)據(jù)返回 JavaScript 代碼(仍然在 Web 表單中),后者決定如何處理這些數(shù)據(jù)。它可以迅速更新表單數(shù)據(jù),讓人感覺應(yīng)用程序是立即完成的,表單沒有提交或刷新而用戶得到了新數(shù)據(jù)。JavaScript 代碼甚至可以對收到的數(shù)據(jù)執(zhí)行某種計(jì)算,再發(fā)送另一個(gè)請求,完全不需要用戶干預(yù)!這就是 XMLHttpRequest 的強(qiáng)大之處。它可以根據(jù)需要自行與服務(wù)器進(jìn)行交互,用戶甚至可以完全不知道幕后發(fā)生的一切。結(jié)果就是類似于桌面應(yīng)用程序的動(dòng)態(tài)、快速響應(yīng)、高交互性的體驗(yàn),但是背后又擁有互聯(lián)網(wǎng)的全部強(qiáng)大力量。

加入一些 JavaScript

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

·獲取表單數(shù)據(jù):JavaScript 代碼很容易從 HTML 表單中抽取數(shù)據(jù)并發(fā)送到服務(wù)器。?
·修改表單上的數(shù)據(jù):更新表單也很簡單,從設(shè)置字段值到迅速替換圖像。?
·解析 HTML 和 XML:使用 JavaScript 代碼操縱 DOM(請參閱 下一節(jié)),處理 HTML 表單服務(wù)器返回的 XML 數(shù)據(jù)的結(jié)構(gòu)。?

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

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


// 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];
這里沒有特別需要注意的地方,真是好極了!您應(yīng)該認(rèn)識(shí)到這里并沒有非常復(fù)雜的東西。只要掌握了 XMLHttpRequest,Ajax 應(yīng)用程序的其他部分就是如 清單 2 所示的簡單 JavaScript 代碼了,混合有少量的 HTML。同時(shí),還要用一點(diǎn)兒 DOM,我們就來看看吧。

以 DOM 結(jié)束

最后還有 DOM,即文檔對象模型。可能對有些讀者來說 DOM 有點(diǎn)兒令人生畏,HTML 設(shè)計(jì)者很少使用它,即使 JavaScript 程序員也不大用到它,除非要完成某項(xiàng)高端編程任務(wù)。大量使用 DOM 的是 復(fù)雜的 Java 和 C/C++ 程序,這可能就是 DOM 被認(rèn)為難以學(xué)習(xí)的原因。

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

獲取 Request 對象

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

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

使用 Microsoft 瀏覽器

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

但并不是這么簡單。根據(jù) Internet Explorer 中安裝的 JavaScript 技術(shù)版本不同,MSXML 實(shí)際上有兩種不同的版本,因此必須對這兩種情況分別編寫代碼。請參閱 清單 3,其中的代碼在 Microsoft 瀏覽器上創(chuàng)建了一個(gè) XMLHttpRequest。

清單 3. 在 Microsoft 瀏覽器上創(chuàng)建 XMLHttpRequest 對象

var xmlHttp = false;
try {
??xmlHttp = new ActiveXObject("Msxml2.XMLHTTP");
} catch (e) {
??try {
? ? xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
??} catch (e2) {
? ? xmlHttp = false;
??}
}
您對這些代碼可能還不完全理解,但沒有關(guān)系。當(dāng)本系列文章結(jié)束的時(shí)候,您將對 JavaScript 編程、錯(cuò)誤處理、條件編譯等有更深的了解。現(xiàn)在只要牢牢記住其中的兩行代碼:

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



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

這兩行代碼基本上就是嘗試使用一個(gè)版本的 MSXML 創(chuàng)建對象,如果失敗則使用另一個(gè)版本創(chuàng)建該對象。不錯(cuò)吧?如果都不成功,則將 xmlHttp 變量設(shè)為 false,告訴您的代碼出現(xiàn)了問題。如果出現(xiàn)這種情況,可能是因?yàn)榘惭b了非 Microsoft 瀏覽器,需要使用不同的代碼。

處理 Mozilla 和非 Microsoft 瀏覽器

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

var xmlHttp = new XMLHttpRequest object;。

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

結(jié)合起來

關(guān)鍵是要支持所有 瀏覽器。誰愿意編寫一個(gè)只能用于 Internet Explorer 或者非 Microsoft 瀏覽器的應(yīng)用程序呢?或者更糟,要編寫一個(gè)應(yīng)用程序兩次?當(dāng)然不!因此代碼要同時(shí)支持 Internet Explorer 和非 Microsoft 瀏覽器。清單 4 顯示了這樣的代碼。

清單 4. 以支持多種瀏覽器的方式創(chuàng)建 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();
}
現(xiàn)在先不管那些注釋掉的奇怪符號,如 @cc_on,這是特殊的 JavaScript 編譯器命令,將在下一期針對 XMLHttpRequest 的文章中詳細(xì)討論。這段代碼的核心分為三步:

1、建立一個(gè)變量 xmlHttp 來引用即將創(chuàng)建的 XMLHttpRequest 對象。?
2、嘗試在 Microsoft 瀏覽器中創(chuàng)建該對象:?
? ?? ?1)嘗試使用 Msxml2.XMLHTTP 對象創(chuàng)建它。?
? ?? ?2)如果失敗,再嘗試 Microsoft.XMLHTTP 對象。?
2、如果仍然沒有建立 xmlHttp,則以非 Microsoft 的方式創(chuàng)建該對象。?
最后,xmlHttp 應(yīng)該引用一個(gè)有效的 XMLHttpRequest 對象,無論運(yùn)行什么樣的瀏覽器。

關(guān)于安全性的一點(diǎn)說明

安全性如何呢?現(xiàn)在瀏覽器允許用戶提高他們的安全等級,關(guān)閉 JavaScript 技術(shù),禁用瀏覽器中的任何選項(xiàng)。在這種情況下,代碼無論如何都不會(huì)工作。此時(shí)必須適當(dāng)?shù)靥幚韱栴},這需要單獨(dú)的一篇文章來討論,要放到以后了(這個(gè)系列夠長了吧?不用擔(dān)心,讀完之前也許您就掌握了)。現(xiàn)在要編寫一段健壯但不夠完美的代碼,對于掌握 Ajax 來說就很好了。以后我們還將討論更多的細(xì)節(jié)。

Ajax 世界中的請求/響應(yīng)

現(xiàn)在我們介紹了 Ajax,對 XMLHttpRequest 對象以及如何創(chuàng)建它也有了基本的了解。如果閱讀得很仔細(xì),您可能已經(jīng)知道與服務(wù)器上的 Web 應(yīng)用程序打交道的是 JavaScript 技術(shù),而不是直接提交給那個(gè)應(yīng)用程序的 HTML 表單。

還缺少什么呢?到底如何使用 XMLHttpRequest。因?yàn)檫@段代碼非常重要,您編寫的每個(gè) Ajax 應(yīng)用程序都要以某種形式使用它,先看看 Ajax 的基本請求/響應(yīng)模型是什么樣吧。

發(fā)出請求

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

1、從 Web 表單中獲取需要的數(shù)據(jù)。?
2、建立要連接的 URL。?
3、打開到服務(wù)器的連接。?
4、設(shè)置服務(wù)器在完成后要運(yùn)行的函數(shù)。?
5、發(fā)送請求。?

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

清單 5. 發(fā)出 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 代碼獲取幾個(gè)表單字段的值。然后設(shè)置一個(gè) PHP 腳本作為鏈接的目標(biāo)。要注意腳本 URL 的指定方式,city 和 state(來自表單)使用簡單的 GET 參數(shù)附加在 URL 之后。

然后打開一個(gè)連接,這是您第一次看到使用 XMLHttpRequest。其中指定了連接方法(GET)和要連接的 URL。最后一個(gè)參數(shù)如果設(shè)為 true,那么將請求一個(gè)異步連接(這就是 Ajax 的由來)。如果使用 false,那么代碼發(fā)出請求后將等待服務(wù)器返回的響應(yīng)。如果設(shè)為 true,當(dāng)服務(wù)器在后臺(tái)處理請求的時(shí)候用戶仍然可以使用表單(甚至調(diào)用其他 JavaScript 方法)。

xmlHttp(要記住,這是 XMLHttpRequest 對象實(shí)例)的 onreadystatechange 屬性可以告訴服務(wù)器在運(yùn)行完成 后(可能要用五分鐘或者五個(gè)小時(shí))做什么。因?yàn)榇a沒有等待服務(wù)器,必須讓服務(wù)器知道怎么做以便您能作出響應(yīng)。在這個(gè)示例中,如果服務(wù)器處理完了請求,一個(gè)特殊的名為 updatePage() 的方法將被觸發(fā)。

最后,使用值 null 調(diào)用 send()。因?yàn)橐呀?jīng)在請求 URL 中添加了要發(fā)送給服務(wù)器的數(shù)據(jù)(city 和 state),所以請求中不需要發(fā)送任何數(shù)據(jù)。這樣就發(fā)出了請求,服務(wù)器按照您的要求工作。

如果沒有發(fā)現(xiàn)任何新鮮的東西,您應(yīng)該體會(huì)到這是多么簡單明了!除了牢牢記住 Ajax 的異步特性外,這些內(nèi)容都相當(dāng)簡單。應(yīng)該感激 Ajax 使您能夠?qū)P木帉懫恋膽?yīng)用程序和界面,而不用擔(dān)心復(fù)雜的 HTTP 請求/響應(yīng)代碼。

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

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

處理響應(yīng)

現(xiàn)在要面對服務(wù)器的響應(yīng)了。現(xiàn)在只要知道兩點(diǎn):

·什么也不要做,直到 xmlHttp.readyState 屬性的值等于 4。?
·服務(wù)器將把響應(yīng)填充到 xmlHttp.responseText 屬性中。?

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

清單 6. 處理服務(wù)器響應(yīng)

function updatePage() {
??if (xmlHttp.readyState == 4) {
? ? var response = xmlHttp.responseText;
? ? document.getElementById("zipCode").value = response;
??}
}
這些代碼同樣既不難也不復(fù)雜。它等待服務(wù)器調(diào)用,如果是就緒狀態(tài),則使用服務(wù)器返回的值(這里是用戶輸入的城市和州的 ZIP 編碼)設(shè)置另一個(gè)表單字段的值。于是包含 ZIP 編碼的 zipCode 字段突然出現(xiàn)了,而用戶沒有按任何按鈕!這就是前面所說的桌面應(yīng)用程序的感覺。快速響應(yīng)、動(dòng)態(tài)感受等等,這些都只因?yàn)橛辛诵⌒〉囊欢?Ajax 代碼。

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

連接 Web 表單

還有什么呢?實(shí)際上沒有多少了。一個(gè) JavaScript 方法捕捉用戶輸入表單的信息并將其發(fā)送到服務(wù)器,另一個(gè) JavaScript 方法監(jiān)聽和處理響應(yīng),并在響應(yīng)返回時(shí)設(shè)置字段的值。所有這些實(shí)際上都依賴于調(diào)用 第一個(gè) JavaScript 方法,它啟動(dòng)了整個(gè)過程。最明顯的辦法是在 HTML 表單中增加一個(gè)按鈕,但這是 2001 年的辦法,您不這樣認(rèn)為嗎?還是像 清單 7 這樣利用 JavaScript 技術(shù)吧。

清單 7. 啟動(dòng)一個(gè) 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>
如果感覺這像是一段相當(dāng)普通的代碼,那就對了,正是如此!當(dāng)用戶在 city 或 state 字段中輸入新的值時(shí),callServer() 方法就被觸發(fā),于是 Ajax 開始運(yùn)行了。有點(diǎn)兒明白怎么回事了吧?好,就是如此!

結(jié)束語

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

現(xiàn)在先花點(diǎn)兒時(shí)間考慮考慮 Ajax 應(yīng)用程序有多么強(qiáng)大。設(shè)想一下,當(dāng)單擊按鈕、輸入一個(gè)字段、從組合框中選擇一個(gè)選項(xiàng)或者用鼠標(biāo)在屏幕上拖動(dòng)時(shí),Web 表單能夠立刻作出響應(yīng)會(huì)是什么情形。想一想異步 究竟意味著什么,想一想 JavaScript 代碼運(yùn)行而且不等待 服務(wù)器對它的請求作出響應(yīng)。會(huì)遇到什么樣的問題?會(huì)進(jìn)入什么樣的領(lǐng)域?考慮到這種新的方法,編程的時(shí)候應(yīng)如何改變表單的設(shè)計(jì)?

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


第 2 頁 使用 JavaScript 和 Ajax 發(fā)出異步請求

多數(shù) Web 應(yīng)用程序都使用請求/響應(yīng)模型從服務(wù)器上獲得完整的 HTML 頁面。常常是點(diǎn)擊一個(gè)按鈕,等待服務(wù)器響應(yīng),再點(diǎn)擊另一個(gè)按鈕,然后再等待,這樣一個(gè)反復(fù)的過程。有了 Ajax 和 XMLHttpRequest 對象,就可以使用不必讓用戶等待服務(wù)器響應(yīng)的請求/響應(yīng)模型了。本文中,Brett McLaughlin 介紹了如何創(chuàng)建能夠適應(yīng)不同瀏覽器的 XMLHttpRequest 實(shí)例,建立和發(fā)送請求,并響應(yīng)服務(wù)器。

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

本文中,您將開始接觸最基本和基礎(chǔ)性的有關(guān) Ajax 的全部對象和編程方法:XMLHttpRequest 對象。該對象實(shí)際上僅僅是一個(gè)跨越所有 Ajax 應(yīng)用程序的公共線程,您可能已經(jīng)預(yù)料到,只有徹底理解該對象才能充分發(fā)揮編程的潛力。事實(shí)上,有時(shí)您會(huì)發(fā)現(xiàn),要正確地使用 XMLHttpRequest,顯然不能 使用 XMLHttpRequest。這到底是怎么回事呢?

Web 2.0 一瞥

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

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

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

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

XMLHttpRequest 簡介

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

·open():建立到服務(wù)器的新請求。?
·send():向服務(wù)器發(fā)送請求。?
·abort():退出當(dāng)前請求。?
·readyState:提供當(dāng)前 HTML 的就緒狀態(tài)。?
·responseText:服務(wù)器返回的請求響應(yīng)文本。?

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

簡單的 new

首先需要?jiǎng)?chuàng)建一個(gè)新變量并賦給它一個(gè) XMLHttpRequest 對象實(shí)例。這在 JavaScript 中很簡單,只要對該對象名使用 new 關(guān)鍵字即可,如 清單 1 所示。

清單 1. 創(chuàng)建新的 XMLHttpRequest 對象

<script language="javascript" type="text/javascript">
var request = new XMLHttpRequest();
</script>
不難吧?記住,JavaScript 不要求指定變量類型,因此不需要像 清單 2 那樣做(在 Java 語言中可能需要這樣)。

清單 2. 創(chuàng)建 XMLHttpRequest 的 Java 偽代碼

XMLHttpRequest request = new XMLHttpRequest();
因此在 JavaScript 中用 var 創(chuàng)建一個(gè)變量,給它一個(gè)名字(如 “request”),然后賦給它一個(gè)新的 XMLHttpRequest 實(shí)例。此后就可以在函數(shù)中使用該對象了。

錯(cuò)誤處理

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

清單 3. 創(chuàng)建具有錯(cuò)誤處理能力的 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、創(chuàng)建一個(gè)新變量 request 并賦值 false。后面將使用 false 作為判定條件,它表示還沒有創(chuàng)建 XMLHttpRequest 對象。?
2、增加 try/catch 塊:?
? ???1)嘗試創(chuàng)建 XMLHttpRequest 對象。?
? ???2)如果失敗(catch (failed))則保證 request 的值仍然為 false。?
3、檢查 request 是否仍為 false(如果一切正常就不會(huì)是 false)。?
4、如果出現(xiàn)問題(request 是 false)則使用 JavaScript 警告通知用戶出現(xiàn)了問題。?

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

應(yīng)付 Microsoft

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

圖 1. Internet Explorer 報(bào)告錯(cuò)誤



顯然有什么地方不對勁,而 Internet Explorer 很難說是一種過時(shí)的瀏覽器,因?yàn)槿澜缬?70% 在使用 Internet Explorer。換句話說,如果不支持 Microsoft 和 Internet Explorer 就不會(huì)受到 Web 世界的歡迎!因此我們需要采用不同的方法處理 Microsoft 瀏覽器。

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

Microsoft 參與了嗎?

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


清單 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、創(chuàng)建一個(gè)新變量 request 并賦值 false。使用 false 作為判斷條件,它表示還沒有創(chuàng)建 XMLHttpRequest 對象。?
2、增加 try/catch 塊:?
? ? 1)嘗試創(chuàng)建 XMLHttpRequest 對象。?
? ? 2)如果失敗(catch (trymicrosoft)):?
? ?? ?? ?? ?1>嘗試使用較新版本的 Microsoft 瀏覽器創(chuàng)建 Microsoft 兼容的對象(Msxml2.XMLHTTP)。?
? ?? ?? ?? ?2> 如果失敗(catch (othermicrosoft))嘗試使用較老版本的 Microsoft 瀏覽器創(chuàng)建 Microsoft 兼容的對象(Microsoft.XMLHTTP)。?
? ? 2)如果失敗(catch (failed))則保證 request 的值仍然為 false。?
3、檢查 request 是否仍然為 false(如果一切順利就不會(huì)是 false)。?
4、如果出現(xiàn)問題(request 是 false)則使用 JavaScript 警告通知用戶出現(xiàn)了問題。?

這樣修改代碼之后再使用 Internet Explorer 試驗(yàn),就應(yīng)該看到已經(jīng)創(chuàng)建的表單(沒有錯(cuò)誤消息)。我實(shí)驗(yàn)的結(jié)果如 圖 2 所示。

圖 2. Internet Explorer 正常工作



靜態(tài)與動(dòng)態(tài)

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

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

清單 5. 將 XMLHttpRequest 創(chuàng)建代碼移動(dòng)到方法中


<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 之前需要調(diào)用該方法。因此還需要 清單 6 這樣的代碼。

清單 6. 使用 XMLHttpRequest 的創(chuàng)建方法


<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>

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

如果使用靜態(tài) JavaScript,用戶在點(diǎn)擊頁面的時(shí)候很快就會(huì)看到錯(cuò)誤信息。這樣也很煩人,是不是?可能令用戶錯(cuò)誤地認(rèn)為您的 Web 應(yīng)用程序不能在他的瀏覽器上運(yùn)行。不過,當(dāng)然要比他們花費(fèi)了 10 分鐘輸入信息之后再顯示同樣的錯(cuò)誤要好。因此,我建議編寫靜態(tài)的代碼,讓用戶盡可能早地發(fā)現(xiàn)問題。

用 XMLHttpRequest 發(fā)送請求

得到請求對象之后就可以進(jìn)入請求/響應(yīng)循環(huán)了。記住,XMLHttpRequest 惟一的目的是讓您發(fā)送請求和接收響應(yīng)。其他一切都是 JavaScript、CSS 或頁面中其他代碼的工作:改變用戶界面、切換圖像、解釋服務(wù)器返回的數(shù)據(jù)。準(zhǔn)備好 XMLHttpRequest 之后,就可以向服務(wù)器發(fā)送請求了。

歡迎使用沙箱

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

設(shè)置服務(wù)器 URL

首先要確定連接的服務(wù)器的 URL。這并不是 Ajax 的特殊要求,但仍然是建立連接所必需的,顯然現(xiàn)在您應(yīng)該知道如何構(gòu)造 URL 了。多數(shù)應(yīng)用程序中都會(huì)結(jié)合一些靜態(tài)數(shù)據(jù)和用戶處理的表單中的數(shù)據(jù)來構(gòu)造該 URL。比如,清單 7 中的 JavaScript 代碼獲取電話號碼字段的值并用其構(gòu)造 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>
這里沒有難懂的地方。首先,代碼創(chuàng)建了一個(gè)新變量 phone,并把 ID 為 “phone” 的表單字段的值賦給它。清單 8 展示了這個(gè)表單的 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>
還要注意,當(dāng)用戶輸入電話號碼或者改變電話號碼時(shí),將觸發(fā) 清單 8 所示的 getCustomerInfo() 方法。該方法取得電話號碼并構(gòu)造存儲(chǔ)在 url 變量中的 URL 字符串。記住,由于 Ajax 代碼是沙箱型的,因而只能連接到同一個(gè)域,實(shí)際上 URL 中不需要域名。該例中的腳本名為 /cgi-local/lookupCustomer.php。最后,電話號碼作為 GET 參數(shù)附加到該腳本中:"phone=" + escape(phone)。

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

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

打開請求

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

request-type:發(fā)送請求的類型。典型的值是 GET 或 POST,但也可以發(fā)送 HEAD 請求。?
url:要連接的 URL。?
asynch:如果希望使用異步連接則為 true,否則為 false。該參數(shù)是可選的,默認(rèn)為 true。?
username:如果需要身份驗(yàn)證,則可以在此指定用戶名。該可選參數(shù)沒有默認(rèn)值。 password:如果需要身份驗(yàn)證,則可以在此指定口令。該可選參數(shù)沒有默認(rèn)值。?

open() 是打開嗎?
Internet 開發(fā)人員對 open() 方法到底做什么沒有達(dá)成一致。但它實(shí)際上并不是 打開一個(gè)請求。如果監(jiān)控 XHTML/Ajax 頁面及其連接腳本之間的網(wǎng)絡(luò)和數(shù)據(jù)傳遞,當(dāng)調(diào)用 open() 方法時(shí)將看不到任何通信。不清楚為何選用了這個(gè)名字,但顯然不是一個(gè)好的選擇。?

通常使用其中的前三個(gè)參數(shù)。事實(shí)上,即使需要異步連接,也應(yīng)該指定第三個(gè)參數(shù)為 “true”。這是默認(rèn)值,但堅(jiān)持明確指定請求是異步的還是同步的更容易理解。

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

清單 9. 打開請求

? ?function getCustomerInfo() {
? ???var phone = document.getElementById("phone").value;
? ???var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);
? ???request.open("GET", url, true);
? ?}
一旦設(shè)置好了 URL,其他就簡單了。多數(shù)請求使用 GET 就夠了(后面的文章中將看到需要使用 POST 的情況),再加上 URL,這就是使用 open() 方法需要的全部內(nèi)容了。

挑戰(zhàn)異步性

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

·沙漏(特別是 Windows 上)。?
·旋轉(zhuǎn)的皮球(通常在 Mac 機(jī)器上)。?
·應(yīng)用程序基本上凍結(jié)了,然后過一段時(shí)間光標(biāo)變化了。?

這正是 Web 應(yīng)用程序讓人感到笨拙或緩慢的原因 —— 缺乏真正的交互性。按下按鈕時(shí),應(yīng)用程序?qū)嶋H上變得不能使用,直到剛剛觸發(fā)的請求得到響應(yīng)。如果請求需要大量服務(wù)器處理,那么等待的時(shí)間可能很長(至少在這個(gè)多處理器、DSL 沒有等待的世界中是如此)。

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

發(fā)送請求

一旦用 open() 配置好之后,就可以發(fā)送請求了。幸運(yùn)的是,發(fā)送請求的方法的名稱要比 open() 適當(dāng),它就是 send()。

send() 只有一個(gè)參數(shù),就是要發(fā)送的內(nèi)容。但是在考慮這個(gè)方法之前,回想一下前面已經(jīng)通過 URL 本身發(fā)送過數(shù)據(jù)了:

var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);
雖然可以使用 send() 發(fā)送數(shù)據(jù),但也能通過 URL 本身發(fā)送數(shù)據(jù)。事實(shí)上,GET 請求(在典型的 Ajax 應(yīng)用中大約占 80%)中,用 URL 發(fā)送數(shù)據(jù)要容易得多。如果需要發(fā)送安全信息或 XML,可能要考慮使用 send() 發(fā)送內(nèi)容(本系列的后續(xù)文章中將討論安全數(shù)據(jù)和 XML 消息)。如果不需要通過 send() 傳遞數(shù)據(jù),則只要傳遞 null 作為該方法的參數(shù)即可。因此您會(huì)發(fā)現(xiàn)在本文中的例子中只需要這樣發(fā)送請求(參見 清單 10)。

清單 10. 發(fā)送請求

? ?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);
? ?}
指定回調(diào)方法

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

首先一定要理解這些代碼中的流程(如果需要請回顧 清單 10)。建立其請求然后發(fā)出請求。此外,因?yàn)槭钱惒秸埱?#xff0c;所以 JavaScript 方法(例子中的 getCustomerInfo())不會(huì)等待服務(wù)器。因此代碼將繼續(xù)執(zhí)行,就是說,將退出該方法而把控制返回給表單。用戶可以繼續(xù)輸入信息,應(yīng)用程序不會(huì)等待服務(wù)器。

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

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


清單 11. 設(shè)置回調(diào)方法

? ?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);
? ?}
需要特別注意的是該屬性在代碼中設(shè)置的位置 —— 它是在調(diào)用 send() 之前 設(shè)置的。發(fā)送請求之前必須設(shè)置該屬性,這樣服務(wù)器在回答完成請求之后才能查看該屬性。現(xiàn)在剩下的就只有編寫 updatePage() 方法了,這是本文最后一節(jié)要討論的重點(diǎn)。

處理服務(wù)器響應(yīng)

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

回調(diào)和 Ajax

現(xiàn)在我們已經(jīng)看到如何告訴服務(wù)器完成后應(yīng)該做什么:將 XMLHttpRequest 對象的 onreadystatechange 屬性設(shè)置為要運(yùn)行的函數(shù)名。這樣,當(dāng)服務(wù)器處理完請求后就會(huì)自動(dòng)調(diào)用該函數(shù)。也不需要擔(dān)心該函數(shù)的任何參數(shù)。我們從一個(gè)簡單的方法開始,如 清單 12 所示。

清單 12. 回調(diào)方法的代碼


<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>
它僅僅發(fā)出一些簡單的警告,告訴您服務(wù)器什么時(shí)候完成了任務(wù)。在自己的網(wǎng)頁中試驗(yàn)這些代碼,然后在瀏覽器中打開(如果希望查看該例中的 XHTML,請參閱 清單 8)。輸入電話號碼然后離開該字段,將看到一個(gè)彈出的警告窗口(如 圖 3 所示),但是點(diǎn)擊 OK 又出現(xiàn)了……

圖 3. 彈出警告的 Ajax 代碼



根據(jù)瀏覽器的不同,在表單停止彈出警告之前會(huì)看到兩次、三次甚至四次警告。這是怎么回事呢?原來我們還沒有考慮 HTTP 就緒狀態(tài),這是請求/響應(yīng)循環(huán)中的一個(gè)重要部分。

HTTP 就緒狀態(tài)

前面提到,服務(wù)器在完成請求之后會(huì)在 XMLHttpRequest 的 onreadystatechange 屬性中查找要調(diào)用的方法。這是真的,但還不完整。事實(shí)上,每當(dāng) HTTP 就緒狀態(tài)改變時(shí)它都會(huì)調(diào)用該方法。這意味著什么呢?首先必須理解 HTTP 就緒狀態(tài)。

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

·0:請求沒有發(fā)出(在調(diào)用 open() 之前)。?
·1:請求已經(jīng)建立但還沒有發(fā)出(調(diào)用 send() 之前)。?
·2:請求已經(jīng)發(fā)出正在處理之中(這里通常可以從響應(yīng)得到內(nèi)容頭部)。?
·3:請求已經(jīng)處理,響應(yīng)中通常有部分?jǐn)?shù)據(jù)可用,但是服務(wù)器還沒有完成響應(yīng)。?
·4:響應(yīng)已完成,可以訪問服務(wù)器響應(yīng)并使用它。?

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

對于 Ajax 編程,需要直接處理的惟一狀態(tài)就是就緒狀態(tài) 4,它表示服務(wù)器響應(yīng)已經(jīng)完成,可以安全地使用響應(yīng)數(shù)據(jù)了。基于此,回調(diào)方法中的第一行應(yīng)該如 清單 13 所示。

清單 13. 檢查就緒狀態(tài)

? ?function updatePage() {
? ???if (request.readyState == 4)
? ?? ? alert("Server is done!");
? ?}
修改后就可以保證服務(wù)器的處理已經(jīng)完成。嘗試運(yùn)行新版本的 Ajax 代碼,現(xiàn)在就會(huì)看到與預(yù)期的一樣,只顯示一次警告信息了。

HTTP 狀態(tài)碼

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

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

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

清單 14. 檢查 HTTP 狀態(tài)碼

? ?function updatePage() {
? ???if (request.readyState == 4)
? ?? ? if (request.status == 200)
? ?? ?? ?alert("Server is done!");
? ?}
為了增加更健壯的錯(cuò)誤處理并盡量避免過于復(fù)雜,可以增加一兩個(gè)狀態(tài)碼檢查,請看一看 清單 15 中修改后的 updatePage() 版本。

清單 15. 增加一點(diǎn)錯(cuò)誤檢查

? ?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);
? ?}
現(xiàn)在將 getCustomerInfo() 中的 URL 改為不存在的 URL 看看會(huì)發(fā)生什么。應(yīng)該會(huì)看到警告信息說明要求的 URL 不存在 —— 好極了!很難處理所有的錯(cuò)誤條件,但是這一小小的改變能夠涵蓋典型 Web 應(yīng)用程序中 80% 的問題。

讀取響應(yīng)文本

現(xiàn)在可以確保請求已經(jīng)處理完成(通過就緒狀態(tài)),服務(wù)器給出了正常的響應(yīng)(通過狀態(tài)碼),最后我們可以處理服務(wù)器返回的數(shù)據(jù)了。返回的數(shù)據(jù)保存在 XMLHttpRequest 對象的 responseText 屬性中。

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

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

清單 16. 處理服務(wù)器響應(yīng)

? ?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() 方法從管道符分開。得到的數(shù)組放到 response 中。數(shù)組中的第一個(gè)值 —— 上一個(gè)訂單 —— 用 response[0] 訪問,被設(shè)置為 ID 為 “order” 的字段的值。第二個(gè)值 response[1],即客戶地址,則需要更多一點(diǎn)處理。因?yàn)榈刂分械男杏靡话愕男蟹指舴?#xff08;“\n”字符)分隔,代碼中需要用 XHTML 風(fēng)格的行分隔符 <br /> 來代替。替換過程使用 replace() 函數(shù)和正則表達(dá)式完成。最后,修改后的文本作為 HTML 表單 div 中的內(nèi)部 HTML。結(jié)果就是表單突然用客戶信息更新了,如圖 4 所示。

圖 4. 收到客戶數(shù)據(jù)后的 Break Neck 表單



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

結(jié)束語

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

再往后我們將介紹常見 Ajax 工具箱。這些工具箱實(shí)際上隱藏了本文所述的很多細(xì)節(jié),使得 Ajax 編程更容易。您也許會(huì)想,既然有這么多工具箱為何還要對底層的細(xì)節(jié)編碼。答案是,如果不知道應(yīng)用程序在做什么,就很難發(fā)現(xiàn)應(yīng)用程序中的問題。

因此不要忽略這些細(xì)節(jié)或者簡單地瀏覽一下,如果便捷華麗的工具箱出現(xiàn)了錯(cuò)誤,您就不必?fù)项^或者發(fā)送郵件請求支持了。如果了解如何直接使用 XMLHttpRequest,就會(huì)發(fā)現(xiàn)很容易調(diào)試和解決最奇怪的問題。只有讓其解決您的問題,工具箱才是好東西。

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

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


第 3 頁 Ajax 中的高級請求和響應(yīng)

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

在本系列的 上篇文章 中,我們將詳細(xì)介紹 XMLHttpRequest 對象,它是 Ajax 應(yīng)用程序的中心,負(fù)責(zé)處理服務(wù)器端應(yīng)用程序和腳本的請求,并處理從服務(wù)器端組件返回的數(shù)據(jù)。由于所有的 Ajax 應(yīng)用程序都要使用 XMLHttpRequest 對象,因此您可能會(huì)希望熟悉這個(gè)對象,從而能夠讓 Ajax 執(zhí)行得更好。

在本文中,我將在上一篇文章的基礎(chǔ)上重點(diǎn)介紹這個(gè)請求對象的 3 個(gè)關(guān)鍵部分的內(nèi)容:

·HTTP 就緒狀態(tài)
·HTTP 狀態(tài)代碼
·可以生成的請求類型

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

下面讓我們首先來看一下 HTTP 就緒狀態(tài)。

深入了解 HTTP 就緒狀態(tài)

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



XMLHttpRequest 或 XMLHttp:換名玫瑰

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

清單 1. 在回調(diào)函數(shù)中處理服務(wù)器的響應(yīng)

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);
? ?}
}
這顯然是就緒狀態(tài)最常見(也是最簡單)的用法。正如您從數(shù)字 "4" 中可以看出的一樣,還有其他幾個(gè)就緒狀態(tài)(您在上一篇文章中也看到過這個(gè)清單 —— 請參見 參考資料):

·0:請求未初始化(還沒有調(diào)用 open())。
·1:請求已經(jīng)建立,但是還沒有發(fā)送(還沒有調(diào)用 send())。
·2:請求已發(fā)送,正在處理中(通常現(xiàn)在可以從響應(yīng)中獲取內(nèi)容頭)。
·3:請求在處理中;通常響應(yīng)中已有部分?jǐn)?shù)據(jù)可用了,但是服務(wù)器還沒有完成響應(yīng)的生成。
·4:響應(yīng)已完成;您可以獲取并使用服務(wù)器的響應(yīng)了。

如果您希望不僅僅是了解 Ajax 編程的基本知識(shí),那么就不但需要知道這些狀態(tài),了解這些狀態(tài)是何時(shí)出現(xiàn)的,以及如何來使用這些狀態(tài)。首先,您需要學(xué)習(xí)在每種就緒狀態(tài)下可能碰到的是哪種請求狀態(tài)。不幸的是,這一點(diǎn)并不直觀,而且會(huì)涉及幾種特殊的情況。

隱秘就緒狀態(tài)

第一種就緒狀態(tài)的特點(diǎn)是 readyState 屬性為 0(readyState == 0),表示未初始化狀態(tài)。一旦對請求對象調(diào)用 open() 之后,這個(gè)屬性就被設(shè)置為 1。由于您通常都是在一對請求進(jìn)行初始化之后就立即調(diào)用 open(),因此很少會(huì)看到 readyState == 0 的狀態(tài)。另外,未初始化的就緒狀態(tài)在實(shí)際的應(yīng)用程序中是沒有真正的用處的。

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

清單 2. 獲取 0 就緒狀態(tài)


? ?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);
? ?}
在這個(gè)簡單的例子中,getSalesData() 是 Web 頁面調(diào)用來啟動(dòng)請求(例如點(diǎn)擊一個(gè)按鈕時(shí))所使用的函數(shù)。注意您必須在調(diào)用 open()之前 來查看就緒狀態(tài)。圖 1 給出了運(yùn)行這個(gè)應(yīng)用程序的結(jié)果。

圖 1. 就緒狀態(tài) 0



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

查看正在處理的請求的就緒狀態(tài)

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

要查看這種狀態(tài)發(fā)生的過程非常簡單。如果就緒狀態(tài)為 4,我們不僅要運(yùn)行回調(diào)函數(shù)中的代碼,而且還要在每次調(diào)用回調(diào)函數(shù)時(shí)都輸出就緒狀態(tài)。 清單 3 給出了一個(gè)實(shí)現(xiàn)這種功能的例子。

當(dāng) 0 等于 4 時(shí)

在多個(gè) JavaScript 函數(shù)都使用相同的請求對象時(shí),您需要檢查就緒狀態(tài) 0 來確保這個(gè)請求對象沒有正在使用,這種機(jī)制會(huì)產(chǎn)生問題。由于 readyState == 4 表示一個(gè)已完成的請求,因此您經(jīng)常會(huì)發(fā)現(xiàn)那些目前沒在使用的處于就緒狀態(tài)的請求對象仍然被設(shè)置成了 4 —— 這是因?yàn)閺姆?wù)器返回來的數(shù)據(jù)已經(jīng)使用過了,但是從它們被設(shè)置為就緒狀態(tài)之后就沒有進(jìn)行任何變化。有一個(gè)函數(shù) abort() 會(huì)重新設(shè)置請求對象,但是這個(gè)函數(shù)卻不是真正為了這個(gè)目的而使用的。如果您 必須 使用多個(gè)函數(shù),最好是為每個(gè)函數(shù)都創(chuàng)建并使用一個(gè)函數(shù),而不是在多個(gè)函數(shù)之間共享相同的對象。


清單 3. 查看就緒狀態(tài)

? ?function updatePage() {
? ???// Output the current ready state
? ???alert("updatePage() called with ready state of " + request.readyState);
? ?}
如果您不確定如何運(yùn)行這個(gè)函數(shù),就需要?jiǎng)?chuàng)建一個(gè)函數(shù),然后在 Web 頁面中調(diào)用這個(gè)函數(shù),并讓它向服務(wù)器端的組件發(fā)送一個(gè)請求(例如 清單 2 給出的函數(shù),或本系列文章的第 1 部分和第 2 部分中給出的例子)。確保在建立請求時(shí),將回調(diào)函數(shù)設(shè)置為 updatePage();要實(shí)現(xiàn)這種設(shè)置,可以將請求對象的 onreadystatechange 屬性設(shè)置為 updatePage()。

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

圖 2. 就緒狀態(tài) 1



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

瀏覽器的不一致性

在對這個(gè)過程有一個(gè)基本的了解之后,請?jiān)囍鴱膸讉€(gè)不同的瀏覽器中訪問您的頁面。您應(yīng)該會(huì)注意到各個(gè)瀏覽器如何處理這些就緒狀態(tài)并不一致。例如,在 Firefox 1.5 中,您會(huì)看到以下就緒狀態(tài):

·1
·2
·3
·4

這并不奇怪,因?yàn)槊總€(gè)請求狀態(tài)都在這里表示出來了。然而,如果您使用 Safari 來訪問相同的應(yīng)用程序,就應(yīng)該看到 —— 或者看不到 —— 一些有趣的事情。下面是在 Safari 2.0.1 中看到的狀態(tài):

·2
·3
·4

Safari 實(shí)際上把第一個(gè)就緒狀態(tài)給丟棄了,也并沒有什么明顯的原因說明為什么要這樣做;不過這就是 Safari 的工作方式。這還說明了一個(gè)重要的問題:盡管在使用服務(wù)器上的數(shù)據(jù)之前確保請求的狀態(tài)為 4 是一個(gè)好主意,但是依賴于每個(gè)過渡期就緒狀態(tài)編寫的代碼的確會(huì)在不同的瀏覽器上得到不同的結(jié)果。

例如,在使用 Opera 8.5 時(shí),所顯示的就緒狀態(tài)情況就更加糟糕了:

·3
·4

最后,Internet Explorer 會(huì)顯示如下狀態(tài):

·1
·2
·3
·4

如果您碰到請求方面的問題,這就是用來發(fā)現(xiàn)問題的 首要之處。最好的方式是在 Internet Explorer 和 Firefox 都進(jìn)行一下測試 —— 您會(huì)看到所有這 4 種狀態(tài),并可以檢查請求的每個(gè)狀態(tài)所處的情況。

接下來我們再來看一下響應(yīng)端的情況。

顯微鏡下的響應(yīng)數(shù)據(jù)

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

清單 4. 使用服務(wù)器上返回的響應(yīng)


? ?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 相當(dāng)簡單;清單 4 稍微有點(diǎn)復(fù)雜,但是它們在開始時(shí)都要檢查就緒狀態(tài),并獲取 responseText 屬性的值。

查看請求的響應(yīng)文本

與就緒狀態(tài)類似,responseText 屬性的值在整個(gè)請求的生命周期中也會(huì)發(fā)生變化。要查看這種變化,請使用如 清單 5 所示的代碼來測試請求的響應(yīng)文本,以及它們的就緒狀態(tài)。

清單 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 + "'");
? ???}
現(xiàn)在在瀏覽器中打開 Web 應(yīng)用程序,并激活您的請求。要更好地看到這段代碼的效果,請使用 Firefox 或 Internet Explorer,因?yàn)檫@兩個(gè)瀏覽器都可以報(bào)告出請求過程中所有可能的就緒狀態(tài)。例如在就緒狀態(tài) 2 中,就沒有定義 responseText (請參見 圖 3);如果 JavaScript 控制臺(tái)也已經(jīng)打開了,您就會(huì)看到一個(gè)錯(cuò)誤。

圖 3. 就緒狀態(tài)為 2 的響應(yīng)文本



不過在就緒狀態(tài) 3 中,服務(wù)器已經(jīng)在 responseText 屬性中放上了一個(gè)值,至少在這個(gè)例子中是這樣(請參見 圖 4)。

圖 4. 就緒狀態(tài)為 3 的響應(yīng)文本



您會(huì)看到就緒狀態(tài)為 3 的響應(yīng)在每個(gè)腳本、每個(gè)服務(wù)器甚至每個(gè)瀏覽器上都是不一樣的。不過,這在調(diào)試應(yīng)用程序中依然是非常有用的。

獲取安全數(shù)據(jù)

所有的文檔和規(guī)范都強(qiáng)調(diào),只有在就緒狀態(tài)為 4 時(shí)數(shù)據(jù)才可以安全使用。相信我,當(dāng)就緒狀態(tài)為 3 時(shí),您很少能找到無法從 responseText 屬性獲取數(shù)據(jù)的情況。然而,在應(yīng)用程序中將自己的邏輯依賴于就緒狀態(tài) 3 可不是什么好主意 —— 一旦您編寫了依賴于就緒狀態(tài) 3 的完整數(shù)據(jù)的的代碼,幾乎就要自己來負(fù)責(zé)當(dāng)時(shí)的數(shù)據(jù)不完整問題了。

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

當(dāng)然,正如您已經(jīng)看到的一樣,這種方法非常聰明,但它是依賴于瀏覽器的。在 Opera 上,您永遠(yuǎn)都不會(huì)看到前兩個(gè)就緒狀態(tài),而在 Safari 上則沒有第一個(gè)(1)。由于這個(gè)原因,我將這段代碼留作練習(xí),而沒有在本文中包括進(jìn)來。

現(xiàn)在應(yīng)該來看一下狀態(tài)代碼了。

深入了解 HTTP 狀態(tài)代碼

有了就緒狀態(tài)和您在 Ajax 編程技術(shù)中學(xué)習(xí)到的服務(wù)器的響應(yīng),您就可以為 Ajax 應(yīng)用程序添加另外一級復(fù)雜性了 —— 這要使用 HTTP 狀態(tài)代碼。這些代碼對于 Ajax 來說并沒有什么新鮮。從 Web 出現(xiàn)以來,它們就已經(jīng)存在了。在 Web 瀏覽器中您可能已經(jīng)看到過幾個(gè)狀態(tài)代碼:

·401:未經(jīng)授權(quán)
·403:禁止
·404:沒找到

您可以找到更多的狀態(tài)代碼(完整清單請參見 參考資料)。要為 Ajax 應(yīng)用程序另外添加一層控制和響應(yīng)(以及更為健壯的錯(cuò)誤處理)機(jī)制,您需要適當(dāng)?shù)夭榭凑埱蠛晚憫?yīng)中的狀態(tài)代碼。

200:一切正常

在很多 Ajax 應(yīng)用程序中,您將看到一個(gè)回調(diào)函數(shù),它負(fù)責(zé)檢查就緒狀態(tài),然后繼續(xù)利用從服務(wù)器響應(yīng)中返回的數(shù)據(jù),如 清單 6 所示。


清單 6. 忽略狀態(tài)代碼的回調(diào)函數(shù)

? ?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 編程來說證明是一種短視而錯(cuò)誤的方法。如果腳本需要認(rèn)證,而請求卻沒有提供有效的證書,那么服務(wù)器就會(huì)返回諸如 403 或 401 之類的錯(cuò)誤代碼。然而,由于服務(wù)器對請求進(jìn)行了應(yīng)答,因此就緒狀態(tài)就被設(shè)置為 4(即使應(yīng)答并不是請求所期望的也是如此)。最終,用戶沒有獲得有效數(shù)據(jù),當(dāng) JavaScript 試圖使用不存在的服務(wù)器數(shù)據(jù)時(shí)就可能會(huì)出現(xiàn)嚴(yán)重的錯(cuò)誤。

它花費(fèi)了最小的努力來確保服務(wù)器不但完成了一個(gè)請求,而且還返回了一個(gè) “一切良好” 的狀態(tài)代碼。這個(gè)代碼是 "200",它是通過 XMLHttpRequest 對象的 status 屬性來報(bào)告的。為了確保服務(wù)器不但完成了一個(gè)請求,而且還報(bào)告了一個(gè) OK 狀態(tài),請?jiān)谀幕卣{(diào)函數(shù)中添加另外一個(gè)檢查功能,如 清單 7 所示。

清單 7. 檢查有效狀態(tài)代碼

? ?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);
? ???}
? ?}
通過添加這幾行代碼,您就可以確認(rèn)是否存在問題,用戶會(huì)看到一個(gè)有用的錯(cuò)誤消息,而不僅僅是看到一個(gè)由斷章取義的數(shù)據(jù)所構(gòu)成的頁面,而沒有任何解釋。

重定向和重新路由

在深入介紹有關(guān)錯(cuò)誤的內(nèi)容之前,我們有必要來討論一下有關(guān)一個(gè)在使用 Ajax 時(shí) 并不需要 關(guān)心的問題 —— 重定向。在 HTTP 狀態(tài)代碼中,這是 300 系列的狀態(tài)代碼,包括:

·301:永久移動(dòng)
·302:找到(請求被重新定向到另外一個(gè) URL/URI 上)
·305:使用代理(請求必須使用一個(gè)代理來訪問所請求的資源)

Ajax 程序員可能并不太關(guān)心有關(guān)重定向的問題,這是由于兩方面的原因:

·首先,Ajax 應(yīng)用程序通常都是為一個(gè)特定的服務(wù)器端腳本、servlet 或應(yīng)用程序而編寫的。對于那些您看不到就消失了的組件來說,Ajax 程序員就不太清楚了。因此有時(shí)您會(huì)知道資源已經(jīng)移動(dòng)了(因?yàn)槟苿?dòng)了它,或者通過某種手段移動(dòng)了它),接下來要修改請求中的 URL,并且不會(huì)再碰到這種結(jié)果了。
更為重要的一個(gè)原因是:Ajax 應(yīng)用程序和請求都是封裝在沙盒中的。這就意味著提供生成 Ajax 請求的 Web 頁面的域必須是對這些請求進(jìn)行響應(yīng)的域。因此 ebay.com 所提供的 Web 頁面就不能對一個(gè)在 amazon.com 上運(yùn)行的腳本生成一個(gè) Ajax 風(fēng)格的請求;在 ibm.com 上的 Ajax 應(yīng)用程序也無法對在 netbeans.org 上運(yùn)行的 servlets 發(fā)出請求。
·結(jié)果是您的請求無法重定向到其他服務(wù)器上,而不會(huì)產(chǎn)生安全性錯(cuò)誤。在這些情況中,您根本就不會(huì)得到狀態(tài)代碼。通常在調(diào)試控制臺(tái)中都會(huì)產(chǎn)生一個(gè) JavaScript 錯(cuò)誤。因此,在對狀態(tài)代碼進(jìn)行充分的考慮之后,您就可以完全忽略重定向代碼的問題了。

結(jié)果是您的請求無法重定向到其他服務(wù)器上,而不會(huì)產(chǎn)生安全性錯(cuò)誤。在這些情況中,您根本就不會(huì)得到狀態(tài)代碼。通常在調(diào)試控制臺(tái)中都會(huì)產(chǎn)生一個(gè) JavaScript 錯(cuò)誤。因此,在對狀態(tài)代碼進(jìn)行充分的考慮之后,您就可以完全忽略重定向代碼的問題了。

錯(cuò)誤

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

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

邊界情況和困難情況

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

對于典型的用戶來說,應(yīng)用程序 100 次都是正常工作的這個(gè)事實(shí)通常都會(huì)被忘記,然而應(yīng)用程序只要一次出錯(cuò)就會(huì)被他們清楚地記住。如果您可以很好地處理邊界情況(或困難情況),就可以為再次訪問站點(diǎn)的用戶提供滿意的回報(bào)。

圖 5. 常見錯(cuò)誤處理



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

清單 8. 檢查有效狀態(tài)代碼

? ?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);
? ???}
? ?}
雖然這依然相當(dāng)簡單,但是它的確多提供了一些有用的信息。圖 6 給出了與 圖 5 相同的錯(cuò)誤,但是這一次錯(cuò)誤處理代碼向用戶或程序員更好地說明了究竟發(fā)生了什么。

圖 6. 特殊錯(cuò)誤處理



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

其他請求類型

如果您真希望控制 XMLHttpRequest 對象,可以考慮最后實(shí)現(xiàn)這種功能 —— 將 HEAD 請求添加到指令中。在前兩篇文章中,我們已經(jīng)介紹了如何生成 GET 請求;在馬上就要發(fā)表的一篇文章中,您會(huì)學(xué)習(xí)有關(guān)使用 POST 請求將數(shù)據(jù)發(fā)送到服務(wù)器上的知識(shí)。不過本著增強(qiáng)錯(cuò)誤處理和信息搜集的精神,您應(yīng)該學(xué)習(xí)如何生成 HEAD 請求。

生成請求

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


清單 9. 使用 Ajax 生成一個(gè) HEAD 請求

? ?function getSalesData() {
? ???createRequest();
? ???var url = "/boards/servlet/UpdateBoardSales";
? ???request.open("HEAD", url, true);
? ???request.onreadystatechange = updatePage;
? ???request.send(null);
? ?}
當(dāng)您這樣生成一個(gè) HEAD 請求時(shí),服務(wù)器并不會(huì)像對 GET 或 POST 請求一樣返回一個(gè)真正的響應(yīng)。相反,服務(wù)器只會(huì)返回資源的 頭(header),這包括響應(yīng)中內(nèi)容最后修改的時(shí)間、請求資源是否存在和很多其他有用信息。您可以在服務(wù)器處理并返回資源之前使用這些信息來了解有關(guān)資源的信息。

對于這種請求您可以做的最簡單的事情就是簡單地輸出所有的響應(yīng)頭的內(nèi)容。這可以讓您了解通過 HEAD 請求可以使用什么。清單 10 提供了一個(gè)簡單的回調(diào)函數(shù),用來輸出從 HEAD 請求中獲得的響應(yīng)頭的內(nèi)容。

清單 10. 輸出從 HEAD 請求中獲得的響應(yīng)頭的內(nèi)容

? ?function updatePage() {
? ???if (request.readyState == 4) {
? ?? ? alert(request.getAllResponseHeaders());
? ???}
? ?}
請參見 圖 7,其中顯示了從一個(gè)向服務(wù)器發(fā)出的 HEAD 請求的簡單 Ajax 應(yīng)用程序返回的響應(yīng)頭。



您可以單獨(dú)使用這些頭(從服務(wù)器類型到內(nèi)容類型)在 Ajax 應(yīng)用程序中提供其他信息或功能。

檢查 URL

您已經(jīng)看到了當(dāng) URL 不存在時(shí)應(yīng)該如何檢查 404 錯(cuò)誤。如果這變成一個(gè)常見的問題 —— 可能是缺少了一個(gè)特定的腳本或 servlet —— 那么您就可能會(huì)希望在生成完整的 GET 或 POST 請求之前來檢查這個(gè) URL。要實(shí)現(xiàn)這種功能,生成一個(gè) HEAD 請求,然后在回調(diào)函數(shù)中檢查 404 錯(cuò)誤;清單 11 給出了一個(gè)簡單的回調(diào)函數(shù)。

清單 11. 檢查某個(gè) 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);
? ?? ? }
? ???}
? ?}
誠實(shí)地說,這段代碼的價(jià)值并不太大。服務(wù)器必須對請求進(jìn)行響應(yīng),并構(gòu)造一個(gè)響應(yīng)來填充內(nèi)容長度的響應(yīng)頭,因此并不能節(jié)省任何處理時(shí)間。另外,這花費(fèi)的時(shí)間與生成請求并使用 HEAD 請求來查看 URL 是否存在所需要的時(shí)間一樣多,因?yàn)樗墒褂?GET 或 POST 的請求,而不僅僅是如 清單 7 所示一樣來處理錯(cuò)誤代碼。不過,有時(shí)確切地了解目前什么可用也是非常有用的;您永遠(yuǎn)不會(huì)知道何時(shí)創(chuàng)造力就會(huì)迸發(fā)或者何時(shí)需要 HEAD 請求!

有用的 HEAD 請求

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

在這些情況中,您只使用了適當(dāng)?shù)念^名,并將其傳遞給 XMLHttpRequest 對象的 getResponseHeader() 方法。因此要獲取響應(yīng)的長度,只需要調(diào)用 request.getResponseHeader("Content-Length");。要獲取內(nèi)容類型,請使用 request.getResponseHeader("Content-Type");。

在很多應(yīng)用程序中,生成 HEAD 請求并沒有增加任何功能,甚至可能會(huì)導(dǎo)致請求速度變慢(通過強(qiáng)制生成一個(gè) HEAD 請求來獲取有關(guān)響應(yīng)的數(shù)據(jù),然后在使用一個(gè) GET 或 POST 請求來真正獲取響應(yīng))。然而,在出現(xiàn)您不確定有關(guān)腳本或服務(wù)器端組件的情況時(shí),使用 HEAD 請求可以獲取一些基本的數(shù)據(jù),而不需要對響應(yīng)數(shù)據(jù)真正進(jìn)行處理,也不需要大量的帶寬來發(fā)送響應(yīng)。

結(jié)束語

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

然而,Web 已經(jīng)不再是只需實(shí)現(xiàn)簡單應(yīng)用程序的地方了;用戶已經(jīng)變得更加高級,客戶期望能夠獲得更好的穩(wěn)定性、更高級的錯(cuò)誤報(bào)告,如果應(yīng)用程序有 1% 的時(shí)間停機(jī),那么經(jīng)理就可能會(huì)因此而被解雇。

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

·如果您可以考慮各種就緒狀態(tài) —— 并且理解了這些就緒狀態(tài)在不同瀏覽器之間的區(qū)別 —— 就可以快速調(diào)試應(yīng)用程序了。您甚至可以基于就緒狀態(tài)而開發(fā)一些創(chuàng)造性的功能,并向用戶和客戶回報(bào)請求的狀態(tài)。
·如果您要對狀態(tài)代碼進(jìn)行控制,就可以設(shè)置應(yīng)用程序來處理腳本錯(cuò)誤、非預(yù)期的響應(yīng)以及邊緣情況。結(jié)果是應(yīng)用程序在所有的時(shí)間都可以正常工作,而不僅僅是只能一切都正常的情況下才能運(yùn)行。
·增加這種生成 HEAD 請求的能力,檢查某個(gè) URL 是否存在,以及確認(rèn)某個(gè)文件是否被修改過,這樣就可以確保用戶可以獲得有效的頁面,用戶所看到的信息都是最新的,(最重要的是)讓他們驚訝這個(gè)應(yīng)用程序是如何健壯和通用。
本文的目的并非是要讓您的應(yīng)用程序顯得十分華麗,而是幫助您去掉黃色聚光燈后重點(diǎn)昭顯文字的美麗,或者外觀更像桌面一樣。盡管這些都是 Ajax 的功能(在后續(xù)幾篇文章中就會(huì)介紹),不過它們卻像是蛋糕表面的一層奶油。如果您可以使用 Ajax 來構(gòu)建一個(gè)堅(jiān)實(shí)的基礎(chǔ),讓應(yīng)用程序可以很好地處理錯(cuò)誤和問題,用戶就會(huì)返回您的站點(diǎn)和應(yīng)用程序。在接下來的文章中,我們將添加這種直觀的技巧,這會(huì)讓客戶興奮得發(fā)抖。

第 4 頁 利用 DOM 進(jìn)行 Web 響應(yīng)

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

與許多 Web 程序員一樣,您可能使用過 HTML。HTML 是程序員開始與 Web 頁面打交道的方式;HTML 通常是他們完成應(yīng)用程序或站點(diǎn)前的最后一步——調(diào)整一些布局、顏色或樣式。不過,雖然經(jīng)常使用 HTML,但對于 HTML 轉(zhuǎn)到瀏覽器呈現(xiàn)在屏幕上時(shí)到底發(fā)生了什么,人們普遍存在誤解。在我分析您認(rèn)為可能發(fā)生的事情及其可能錯(cuò)誤的原因之前,我希望您對設(shè)計(jì)和服務(wù) Web 頁面時(shí)涉及的過程一清二楚:

1、一些人(通常是您!)在文本編輯器或 IDE 中創(chuàng)建 HTML。
2、然后您將 HTML 上載到 Web 服務(wù)器,例如 Apache HTTPD,并將其公開在 Internet 或 intranet 上。
3、用戶用 Firefox 或 SafariA 等瀏覽器請求您的 Web 頁面。
4、用戶的瀏覽器向您的服務(wù)器請求 HTML。
5、瀏覽器將從服務(wù)器接收到的頁面以圖形和文本方式呈現(xiàn);用戶看到并激活 Web 頁面。

這看起來非常基礎(chǔ),但事情很快會(huì)變得有趣起來。事實(shí)上,步驟 4 和步驟 5 之間發(fā)生的巨大數(shù)量的 “填充物(stuff)” 就是本文的焦點(diǎn)。術(shù)語 “填充物” 也十分適用,因?yàn)槎鄶?shù)程序員從來沒有真正考慮過當(dāng)用戶瀏覽器請求顯示標(biāo)記時(shí)到底在標(biāo)記身上發(fā)生了什么。?

·是否瀏覽器只是讀取 HTML 中的文本并將其顯示?
·CSS 呢?尤其是當(dāng) CSS 位于外部文件時(shí)。
·JavaScript 呢?它也通常位于外部文件中。
·瀏覽器如何處理這些項(xiàng),如果將事件處理程序、函數(shù)和樣式映射到該文本標(biāo)記?

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

Web 程序員和標(biāo)記

對于多數(shù)程序員,當(dāng) Web 瀏覽器開始時(shí)他們的工作就結(jié)束了。也就是說,將一個(gè) HTML 文件放入 Web 瀏覽器的目錄上后,您通常就認(rèn)為它已經(jīng)“完成”,而且(滿懷希望地)認(rèn)為再也不會(huì)考慮它!說到編寫干凈、組織良好的頁面時(shí),這也是一個(gè)偉大的目標(biāo);希望您的標(biāo)記跨瀏覽器、用各種版本的 CSS 和 JavaScript 顯示它應(yīng)該顯示的內(nèi)容,一點(diǎn)錯(cuò)都沒有。

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

程序員做什么

作為典型的 Web 程序員,您可能啟動(dòng)文本編輯和 IDE 后就開始輸入 HTML、CSS 甚至 JavaScript。很容易認(rèn)為這些標(biāo)記、選擇器和屬性只是使站點(diǎn)正確顯示而做的小小的任務(wù)。但是,在這一點(diǎn)上您需要拓展您的思路,要意識(shí)到您是在組織您的內(nèi)容。不要擔(dān)心;我保證這不會(huì)變成關(guān)于標(biāo)記美觀、您必須如何認(rèn)識(shí)到 Web 頁面的真正潛力或其他任何元物質(zhì)的講座。您需要了解的是您在 Web 開發(fā)中到底是什么角色。

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

您絕對控制的是 Web 頁面的結(jié)構(gòu)。您的標(biāo)記不可更改,用戶就不能亂弄;他們的瀏覽器只能從您的 Web 服務(wù)器檢索標(biāo)記并顯示它(雖然樣式更符合用戶的品味而不是您自己的品味)。但頁面組織,不管是在該段落內(nèi)還是在其他分區(qū),都只由您單獨(dú)決定。要是想實(shí)際更改您的頁面(這是大多數(shù) Ajax 應(yīng)用程序所關(guān)注的),您操作的是頁面的結(jié)構(gòu)。盡管很容易更改一段文本的顏色,但在現(xiàn)有頁面上添加文本或整個(gè)區(qū)段要難得多。不管用戶如何設(shè)計(jì)該區(qū)段的樣式,都是由您控制頁面本身的組織。

標(biāo)記做什么

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

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

只要牢記您的標(biāo)記只為您的頁面提供組織、框架,您就能立于不敗之地。再前進(jìn)一小步,您就會(huì)明白瀏覽器是如何接受所有的文本組織并將其轉(zhuǎn)變?yōu)槌売腥さ囊恍〇|西的,即一組對象,其中每個(gè)對象都可被更改、添加或刪除。

文本標(biāo)記的優(yōu)點(diǎn)

在討論 Web 瀏覽器之前,值得考慮一下為什么純文本絕對 是存儲(chǔ) HTML 的最佳選擇(有關(guān)詳細(xì)信息,請參閱 有關(guān)標(biāo)記的一些其他想法)。不考慮優(yōu)缺點(diǎn),只是回憶一下在每次查看頁面時(shí) HTML 是通過網(wǎng)絡(luò)發(fā)送到 Web 瀏覽器的(為了簡潔,不考慮高速緩存等)。真是再?zèng)]有比傳遞文本再有效的方法了。二進(jìn)制對象、頁面圖形表示、重新組織的標(biāo)記塊等等,所有這一切都比純文本文件通過網(wǎng)絡(luò)傳遞要更困難。

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

最后但同樣重要的一點(diǎn)是,記住,新標(biāo)準(zhǔn)(比如 HTML 4.01 與 XHTML 1.0 和 1.1)承諾將內(nèi)容(頁面中的數(shù)據(jù))與表示和樣式(通常由 CSS 應(yīng)用)分離。如果程序員要將 HTML 與 CSS 分離,然后強(qiáng)制瀏覽器檢索粘結(jié)頁面各部分的一些頁面表示,這會(huì)失去這些標(biāo)準(zhǔn)的多數(shù)優(yōu)點(diǎn)。保持這些部分到達(dá)瀏覽器時(shí)都一直分離使得瀏覽器在從服務(wù)器獲取 HTML 時(shí)有了前所未有的靈活性。

關(guān)于標(biāo)記的其他想法

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

網(wǎng)絡(luò)上的文本:好東西


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

文本標(biāo)記的缺點(diǎn)

正如文本標(biāo)記對于設(shè)計(jì)人員和頁面創(chuàng)建者具有驚人的優(yōu)點(diǎn)之外,它對于瀏覽器也具有相當(dāng)出奇的缺點(diǎn)。具體來說,瀏覽器很難直接將文本標(biāo)記可視地表示給用戶(詳細(xì)信息請參閱 有關(guān)標(biāo)記的一些其他想法)。考慮下列常見的瀏覽器任務(wù):

·基于元素類型、類、ID 及其在 HTML 文檔中的位置,將 CSS 樣式(通常來自外部文件中的多個(gè)樣式表)應(yīng)用于標(biāo)記。
·基于 JavaScript 代碼(通常位于外部文件)將樣式和格式應(yīng)用于 HTML 文檔的不同部分。
·基于 JavaScript 代碼更改表單字段的值。
·基于 JavaScript 代碼,支持可視效果,比如圖像翻轉(zhuǎn)和圖像交換。

復(fù)雜性并不在于編碼這些任務(wù);其中每件事都是相當(dāng)容易的。復(fù)雜性來自實(shí)際實(shí)現(xiàn)請求動(dòng)作的瀏覽器。如果標(biāo)記存儲(chǔ)為文本,比如,想要在 center-text 類的 p 元素中輸入文本 (text-align: center),如何實(shí)現(xiàn)呢?

·將內(nèi)聯(lián)樣式添加到文本嗎?
·將樣式應(yīng)用到瀏覽器中的 HTML 文本,并只保持內(nèi)容居中或不居中?
·應(yīng)用無樣式的 HTML,然后事后應(yīng)用格式?

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

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

無疑,文本不是答案。它難以修改,為其應(yīng)用樣式和行為很困難,與今天 Web 頁面的動(dòng)態(tài)本質(zhì)在根本上相去甚遠(yuǎn)。

求助于樹視圖

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

清單 1. 文本標(biāo)記中的簡單 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>
瀏覽器接受該頁面并將之轉(zhuǎn)換為樹形結(jié)構(gòu),如圖 1 所示。



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

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

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

一些附加術(shù)語

為了沿用樹的比喻,head 和 body 被叫做 html 的分支(branches)。叫分支是因?yàn)樗鼈冇凶约旱暮⒆印.?dāng)?shù)竭_(dá)樹的末端時(shí),您將進(jìn)入主要的文本,比如 “Trees, trees, everywhere” 和 “really”;這些通常稱為葉子,因?yàn)樗鼈儧]有自己的孩子。您不需要記住所有這些術(shù)語,當(dāng)您試圖弄清楚特定術(shù)語的意思時(shí),只要想像一下樹結(jié)構(gòu)就容易多了。

對象的值

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

對象類型和屬性

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

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

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

someDiv.style.height = "300px";
換句話說,Web 瀏覽器使用對象屬性可以非常容易地更改樹的外觀和結(jié)構(gòu)。將之比作瀏覽器在內(nèi)部將頁面表示為文本時(shí)必須進(jìn)行的復(fù)雜事情,每次更改屬性或結(jié)構(gòu)都需要瀏覽器重新編寫靜態(tài)文件、重新解析并在屏幕上重新顯示。有了對象,所有這一切都解決了。

現(xiàn)在,花點(diǎn)時(shí)間展開一些 HTML 文檔并用樹將其勾畫出來。盡管這看起來是個(gè)不尋常的請求(尤其是在包含極少代碼的這樣一篇文章中),如果您希望能夠操縱這些樹,那么需要熟悉它們的結(jié)構(gòu)。

在這個(gè)過程中,可能會(huì)發(fā)現(xiàn)一些古怪的事情。比如,考慮下列情況:

·屬性發(fā)生了什么?
·分解為元素(比如 em 和 b)的文本呢?
·結(jié)構(gòu)不正確(比如當(dāng)缺少結(jié)束 p 標(biāo)記時(shí))的 HTML 呢?

一旦熟悉這些問題之后,就能更好地理解下面幾節(jié)了。

嚴(yán)格有時(shí)是好事

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

實(shí)際上,p 元素具有三個(gè) 不同的子對象,其中沒有一個(gè)包含完整的 “Welcome to a really boring Web page.” 文本。您會(huì)發(fā)現(xiàn)文本的一部分,比如 “Welcome to a ” 和 “ boring Web page”,但不是全部。為了理解這一點(diǎn),記住標(biāo)記中的任何內(nèi)容都必須轉(zhuǎn)換為某種類型的對象。

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

在本例中,p 元素有三個(gè)不同部分:?

·em 元素之前的文本
·em 元素本身
·em 元素之后的文本

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

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

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

清單 2. 帶有巧妙元素嵌套的標(biāo)記

<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 中將會(huì)找到這些練習(xí)的答案。不要偷看,先花些時(shí)間自動(dòng)解答一下。這樣能幫助您理解組織樹時(shí)應(yīng)用的規(guī)則有多么嚴(yán)格,并真正幫助您掌握 HTML 及其樹結(jié)構(gòu)。

屬性呢?

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

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

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

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

凌亂的 HTML

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

·每個(gè)開始標(biāo)記都有一個(gè)與之匹配的結(jié)束標(biāo)記。所以每個(gè) <p> 在文檔中與 </p> 匹配,每個(gè) <div> 與 </div> 匹配,等等。
·最里面的開始標(biāo)記與最里面的結(jié)束標(biāo)記相匹配,然后次里面的開始標(biāo)記與次里面的結(jié)束標(biāo)記相匹配,依此類推。所以 <b><i>bold and italics</b></i> 是不合法的,因?yàn)樽罾锩娴拈_始標(biāo)記 <i> 與最里面的結(jié)束標(biāo)記 <b> 匹配不當(dāng)。要使之格式良好,要么 切換開始標(biāo)記順序,要么 切換結(jié)束標(biāo)記順序。(如果兩者都切換,則仍會(huì)出現(xiàn)問題)。
深入研究這兩條規(guī)則。這兩條規(guī)則不僅簡化了文檔的組織,還消除了不定性。是否應(yīng)先應(yīng)用粗體后應(yīng)用斜體?或恰恰相反?如果覺得這種順序和不定性不是大問題,那么請記住,CSS 允許規(guī)則覆蓋其他規(guī)則,所以,例如,如果 b 元素中文本的字體不同于 i 元素中的字體,則格式的應(yīng)用順序?qū)⒆兊梅浅V匾R虼?#xff0c;HTML 的格式良好性有著舉足輕重的作用。

如果瀏覽器收到了不是格式良好的文檔,它只會(huì)盡力而為。得到的樹結(jié)構(gòu)在最好情況下將是作者希望的原始頁面的近似,最壞情況下將面目全非。如果您曾將頁面加載到瀏覽器中后看到完全出乎意料的結(jié)果,您可能在看到瀏覽器結(jié)果時(shí)會(huì)猜想您的結(jié)構(gòu)應(yīng)該如何,并沮喪地繼續(xù)工作。當(dāng)然,搞定這個(gè)問題相當(dāng)簡單:確保文檔是格式良好的!如果不清楚如何編寫標(biāo)準(zhǔn)化的 HTML,請咨詢 參考資料 獲得幫助。

DOM 簡介

到目前為止,您已經(jīng)知道瀏覽器將 Web 頁面轉(zhuǎn)換為對象表示,可能您甚至?xí)孪?#xff0c;對象表示是 DOM 樹。DOM 表示 Document Object Model,是一個(gè)規(guī)范,可從 World Wide Web Consortium (W3C) 獲得(您可以參閱 參考資料 中的一些 DOM 相關(guān)鏈接)。

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

文檔對象

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

var domTree = document;
當(dāng)然,該代碼本身沒什么用,但它演示了每個(gè) Web 瀏覽器使得 document 對象可用于 JavaScript 代碼,并演示了對象表示標(biāo)記的完整樹(圖 1)。

每項(xiàng)都是一個(gè)節(jié)點(diǎn)

顯然,document 對象很重要,但這只是開始。在進(jìn)一步深入之前,需要學(xué)習(xí)另一個(gè)術(shù)語:節(jié)點(diǎn)。您已經(jīng)知道標(biāo)記的每個(gè)部分都由一個(gè)對象表示,但它不只是一個(gè)任意的對象,它是特定類型的對象,一個(gè) DOM 節(jié)點(diǎn)。更特定的類型,比如文本、元素和屬性,都繼承自這個(gè)基本的節(jié)點(diǎn)類型。所以可以有文本節(jié)點(diǎn)、元素節(jié)點(diǎn)和屬性節(jié)點(diǎn)。

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

詳細(xì)解釋已經(jīng)學(xué)過的術(shù)語,DOM 樹是對象的樹,但更具體地說,它是節(jié)點(diǎn) 對象的樹。在 Ajax 應(yīng)用程序中或任何其他 JavaScript 中,可以使用這些節(jié)點(diǎn)產(chǎn)生下列效果,比如移除元素及其內(nèi)容,突出顯示特定文本,或添加新圖像元素。因?yàn)槎及l(fā)生在客戶端(運(yùn)行在 Web 瀏覽器中的代碼),所以這些效果立即發(fā)生,而不與服務(wù)器通信。最終結(jié)果通常是應(yīng)用程序感覺起來響應(yīng)更快,因?yàn)楫?dāng)請求轉(zhuǎn)向服務(wù)器時(shí)以及解釋響應(yīng)時(shí),Web 頁面上的內(nèi)容更改不會(huì)出現(xiàn)長時(shí)間的停頓。

在多數(shù)編程語言中,需要學(xué)習(xí)每種節(jié)點(diǎn)類型的實(shí)際對象名稱,學(xué)習(xí)可用的屬性,并弄清楚類型和強(qiáng)制轉(zhuǎn)換;但在 JavaScript 中這都不是必需的。您可以只創(chuàng)建一個(gè)變量,并為它分配您希望的對象(正如您已經(jīng)看到的):

var domTree = document;
var phoneNumberElement = document.getElementById("phone");
var phoneNumber = phoneNumberElement.value;
沒有類型,JavaScript 根據(jù)需要?jiǎng)?chuàng)建變量并為其分配正確的類型。結(jié)果,從 JavaScript 中使用 DOM 變得微不足道(將來有一篇文章會(huì)專門講述與 XML 相關(guān)的 DOM,那時(shí)將更加巧妙)。

結(jié)束語

在這里,我要給您留一點(diǎn)懸念。顯然,這并非是對 DOM 完全詳盡的說明;事實(shí)上,本文不過是 DOM 的簡介。DOM 的內(nèi)容要遠(yuǎn)遠(yuǎn)多于我今天介紹的這些!

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

此時(shí),深入了解 DOM 將十分簡單,比如詳細(xì)設(shè)計(jì)如何在 DOM 樹中移動(dòng)、獲得元素和文本的值、遍歷節(jié)點(diǎn)列表,等等,但這可能會(huì)讓您有這種印象,即 DOM 是關(guān)于代碼的,而事實(shí)上并非如此。

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

如果扎實(shí)掌握了這些概念,然后學(xué)習(xí)了 JavaScript 和 DOM 的語法(下一篇文章),則會(huì)使得響應(yīng)更為容易。

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

圖 2. 清單 2 的答案



圖 3. 清單 3 的答案


轉(zhuǎn)載于:https://www.cnblogs.com/gwq369/p/5479056.html

總結(jié)

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

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。

91视视频在线直接观看在线看网页在线看 | 99精品视频免费观看 | www.玖玖玖 | 精品一区二区在线播放 | 黄色福利网站 | 在线成人中文字幕 | 日日躁夜夜躁xxxxaaaa | 精品久久久999 | 麻豆视频免费入口 | 高清av在线 | 97在线视频免费播放 | 国产成人精品国内自产拍免费看 | 伊香蕉大综综综合久久啪 | 久久精品艹 | 免费视频你懂得 | 欧美综合色在线图区 | 久草网在线视频 | 久久影视网 | 麻豆传媒视频观看 | av电影在线观看完整版一区二区 | 91在线视频精品 | 天无日天天操天天干 | 一本一本久久a久久精品牛牛影视 | 国产视频在线观看一区 | 久久99久久久久久 | 五月天婷婷免费视频 | 国产色视频网站2 | 色婷婷福利 | 国产五月天婷婷 | 精品九九九 | 91黄色免费看 | 婷婷成人亚洲综合国产xv88 | 成人在线播放网站 | 在线观看精品一区 | 欧美日韩视频在线一区 | 高清一区二区三区av | 欧美日本日韩aⅴ在线视频 插插插色综合 | 久草在线91 | 国产在线传媒 | 99视频偷窥在线精品国自产拍 | 日本精品中文字幕 | 国产高清视频色在线www | 国产白浆在线观看 | 一级片免费观看视频 | 国产精品18久久久久久久久 | 久久呀| 91资源在线免费观看 | 久久天 | 97在线影视 | 欧洲不卡av | 婷婷久久综合九色综合 | 狠狠躁日日躁狂躁夜夜躁 | 日本中文字幕电影在线免费观看 | 国产无套精品久久久久久 | 免费99视频 | 开心丁香婷婷深爱五月 | 91丨九色丨国产在线观看 | 国产xxxx做受性欧美88 | 中文字幕在线国产 | 欧美日韩中字 | 国产精品入口66mio女同 | 4p变态网欧美系列 | 91超级碰碰 | 色婷婷综合视频在线观看 | 国产免费久久久久 | 国产色拍| 九九在线高清精品视频 | 人人爱人人添 | 久热只有精品 | 中文字幕久久网 | 欧美另类激情 | 欧美一区二区三区在线 | 国产精品毛片久久久久久久 | 青青色影院| 色婷婷激婷婷情综天天 | 成+人+色综合 | 国产18精品乱码免费看 | sm免费xx网站| 在线播放日韩av | 国产在线免费观看 | 国产精品免费高清 | 日韩欧美xxx | 国产免费人成xvideos视频 | 久久夜色电影 | 国产 日韩 在线 亚洲 字幕 中文 | 成人少妇影院yyyy | av在线之家电影网站 | 久久国产乱 | 国产精品麻豆99久久久久久 | 国产精品成人在线 | 大胆欧美gogo免费视频一二区 | 99精品国产高清在线观看 | 久久久999精品视频 国产美女免费观看 | 国产99在线免费 | 欧美在线aa | 97视频资源| 日韩精品视 | 国产精品ⅴa有声小说 | 日日夜夜天天久久 | 日本精品视频在线播放 | 二区三区毛片 | 国产亲近乱来精品 | 国产精品久久伊人 | 久久综合久久综合这里只有精品 | 一区二区欧美激情 | 久久国产高清视频 | 99在线精品视频在线观看 | 天天射天天舔天天干 | 九九久久婷婷 | 亚洲 欧美变态 另类 综合 | 曰本免费av| 色婷婷综合久久久久中文字幕1 | 成人亚洲网 | 国产午夜激情视频 | 日韩精品免费一区二区三区 | 91在线观看高清 | 色婷婷狠狠五月综合天色拍 | 国产成人在线精品 | 国产裸体bbb视频 | 国产精华国产精品 | 亚洲午夜久久久影院 | 91毛片在线观看 | 一本—道久久a久久精品蜜桃 | 成人av资源网站 | 日韩理论电影在线观看 | 国产精品久久网 | 色综合天天爱 | 国产精品亚州 | 福利视频网站 | 天天干天天干天天干天天干天天干天天干 | 热久在线 | 国产伦精品一区二区三区无广告 | 国产精品99精品久久免费 | 蜜臀久久99静品久久久久久 | 深爱激情综合网 | 久久久久久久亚洲精品 | 99精品在线观看视频 | 免费看成人片 | 天海冀一区二区三区 | 在线中文字母电影观看 | 久久久免费播放 | 高清国产午夜精品久久久久久 | 精品视频在线视频 | 亚洲精品18日本一区app | 97av色| 日韩中文字幕视频在线 | 亚洲精品国产精品乱码在线观看 | 久久久久www| 玖玖精品在线 | 亚洲精品av在线 | 91福利视频一区 | 国产在线传媒 | 日韩欧美在线一区 | 久久精品国产免费看久久精品 | 亚洲天堂社区 | 久久久这里有精品 | 亚洲国产99 | 色资源中文字幕 | 亚洲色图27p | 手机av观看 | 国产手机在线 | 97超碰在线资源 | 欧美性极品xxxx娇小 | 1024手机基地在线观看 | 日韩电影一区二区三区在线观看 | 久久高清视频免费 | 国产精品高潮久久av | 91精品视频免费看 | 五月天欧美精品 | 在线免费观看av网站 | 国内精品一区二区 | 免费a网站| 久久久综合九色合综国产精品 | 国产精品免费久久久久 | 黄色av成人在线 | 视频国产在线观看18 | 久久在线影院 | 国产综合在线观看视频 | 日韩av一区二区三区在线观看 | 91av官网 | 色之综合网 | 天天爱天天干天天爽 | 五月天电影免费在线观看一区 | 亚洲一区二区黄色 | 视频一区二区三区视频 | 免费高清看电视网站 | 亚洲国产操 | 中国一区二区视频 | 亚洲人成免费网站 | 91亚洲精品久久久蜜桃网站 | 夜夜骑日日操 | 夜夜夜夜猛噜噜噜噜噜初音未来 | aaawww| 成人av在线看 | 美女精品久久久 | 美女黄频在线观看 | 国产电影黄色av | 四虎在线免费视频 | av福利免费 | 久久久久久久亚洲精品 | 久久久亚洲精品 | 中文字幕 在线 一 二 | 91亚洲国产成人 | 99视频国产精品免费观看 | 国产精品1区2区 | 国产成人久久77777精品 | 欧美三级免费 | 久久国产欧美日韩 | 久久美女精品 | 色婷婷五 | 亚洲人人射 | 精品在线一区二区三区 | av黄色一级片| 在线观看视频在线 | 日韩中文字幕免费视频 | 91亚色免费视频 | 草在线视频 | 日韩,中文字幕 | 国产精品久久久久一区二区 | 天天综合亚洲 | 亚洲视频在线免费看 | 欧美福利网站 | www.久热| 伊人五月天婷婷 | 日韩理论电影在线观看 | 久久在线观看 | 日本h视频在线观看 | 久久综合狠狠综合久久激情 | 国产成人三级一区二区在线观看一 | 日韩一级片观看 | 午夜av在线电影 | 毛片网站免费 | 日韩免费高清在线观看 | 国产黄色成人 | 园产精品久久久久久久7电影 | 亚洲欧美成aⅴ人在线观看 四虎在线观看 | 欧美精品久久久久久 | 中日韩免费视频 | 国产精品大片在线观看 | 国产精品午夜8888 | 97在线视频观看 | 亚洲欧洲精品视频 | 色wwww| 亚洲天堂视频在线 | 婷婷av在线 | 日韩电影在线观看一区二区三区 | 日韩欧美视频在线播放 | 国产在线精品福利 | 国产青草视频在线观看 | www夜夜操com | 久久久国产精品一区二区中文 | 97免费在线观看视频 | 日韩一级电影在线 | 中文字幕大全 | 久久久久久久久久亚洲精品 | 国产精品视频免费在线观看 | 丁香资源影视免费观看 | 少妇搡bbbb搡bbb搡忠贞 | 亚洲午夜激情网 | 久久公开视频 | 九九精品视频在线观看 | 久久久精品视频网站 | 少妇性xxx| 国产视频久久久 | 高清国产午夜精品久久久久久 | 婷婷综合网 | 欧美日韩高清一区二区三区 | 日韩av不卡在线播放 | 亚洲欧美国产精品18p | 美女视频久久 | 91传媒免费在线观看 | 九九久久免费 | 91丨精品丨蝌蚪丨白丝jk | 色com | 黄色a视频 | 狠狠操影视 | 欧美精品一二 | 麻豆视频在线看 | 国产精品第52页 | 中文字幕av免费观看 | 国产免费久久精品 | 国产盗摄精品一区二区 | 欧美十八 | 久久综合五月天婷婷伊人 | 97超碰福利久久精品 | 精品xxx| 操操碰 | 日韩欧美高清在线 | 久久精品免费 | 国产亚洲精品久久久久久 | 精品国产乱码久久久久 | 91精品在线视频观看 | 日韩av电影中文字幕 | 黄色片网站 | 日韩激情精品 | 国产精品麻豆免费版 | 亚洲四虎在线 | 日韩精品久久久久久 | 天天曰天天曰 | 免费网站在线观看成人 | 免费观看www7722午夜电影 | www.eeuss影院av撸 | 国产亲近乱来精品 | 国产在线专区 | 在线视频 区 | 91精品啪在线观看国产 | 国产精品a成v人在线播放 | 免费看一级黄色大全 | 亚州黄色一级 | 激情五月婷婷综合网 | 久久精品这里都是精品 | av三级在线免费观看 | 久久久久久美女 | 亚洲视频 中文字幕 | 久久99视频| 久久er99热精品一区二区三区 | 在线观看av不卡 | 五月天久久婷 | 欧美一级裸体视频 | 国产成免费视频 | 麻豆久久一区二区 | 日韩mv欧美mv国产精品 | 91福利社区在线观看 | 中文字幕免费在线 | 人人澡人人爽欧一区 | 国产123区在线观看 国产精品麻豆91 | 97色婷婷 | 丁香网五月天 | 精品久久久久久久久久久院品网 | 一区二区三区久久精品 | 中国一级片视频 | 中文字幕在线播放日韩 | 在线免费国产 | 亚洲欧洲精品一区二区 | 日韩免费| 日韩大片在线看 | 日韩视频1区| 日韩在线免费播放 | 天天操天天干天天爱 | 久久高清免费观看 | av在线中文 | 国产视频每日更新 | 久久精品中文字幕一区二区三区 | av不卡免费在线观看 | 国产理论一区二区三区 | 国产精品岛国久久久久久久久红粉 | 亚洲激情校园春色 | 国产精品永久久久久久久www | 色综合久久久久综合体桃花网 | 国产在线精品区 | 2022中文字幕在线观看 | 日韩美在线 | 国产精品一区专区欧美日韩 | 成人午夜黄色 | 在线精品视频免费观看 | 美女av在线免费 | 日韩欧美视频免费在线观看 | 成人av在线播放网站 | 国产精品小视频网站 | 国产一区免费在线 | 国产精品 欧美 日韩 | 精品视频在线播放 | 中文字幕一区二区三 | 日韩经典一区二区三区 | 精品高清美女精品国产区 | 国产精品成人在线 | 夜夜嗨av色一区二区不卡 | 中文字幕国产视频 | 91大片成人网 | 国产精品国产三级国产不产一地 | 久久短视频 | 免费久久久久久 | 国产精品日韩欧美一区二区 | 亚州精品在线视频 | 亚洲综合欧美精品电影 | 成人黄色片免费看 | 免费看国产一级片 | 亚洲午夜久久久久久久久电影网 | 色综合久久88色综合天天6 | 爱爱av网| 日日日爽爽爽 | 久久乐九色婷婷综合色狠狠182 | 欧美激情片在线观看 | 麻豆视频大全 | 国产成人无码AⅤ片在线观 日韩av不卡在线 | 最新国产精品拍自在线播放 | 精品国产亚洲在线 | 丁香婷婷久久久综合精品国产 | 久热香蕉视频 | 国产精品刺激对白麻豆99 | 久久人人97超碰com | 日韩精品五月天 | 精品久久久久久国产91 | 亚洲精品456在线播放第一页 | 欧美一区影院 | 特级西西444www高清大视频 | 在线免费亚洲 | 在线观看一区二区精品 | 精品日本视频 | 高清视频一区二区三区 | 午夜精品电影一区二区在线 | 国产一区二区三区免费视频 | 超碰在线人人 | 免费下载高清毛片 | 中文字幕亚洲不卡 | 超碰97人| 精品99在线观看 | 美女国产在线 | 久久精品一区二区三区国产主播 | 91看片在线播放 | 精品国产美女 | 国产成人一区二区精品非洲 | 奇米影视777影音先锋 | 99视频网址 | 精品亚洲男同gayvideo网站 | 免费精品在线观看 | 国产精品原创av片国产免费 | 成人免费在线网 | 亚洲国产精品成人综合 | 欧美一级免费在线 | 亚洲成a人片综合在线 | 久久久久美女 | 九九热精品视频在线观看 | 97在线免费视频 | av在线免费播放 | 欧美最猛性xxxxx(亚洲精品) | 亚洲涩涩涩 | 中文字幕免费看 | 日韩欧美精品一区二区三区经典 | 国产资源网 | 天天操天天舔天天干 | 亚洲视频精品在线 | 免费看高清毛片 | 国产福利在线免费 | 日韩精品一区二区三区在线视频 | 99视频在线精品 | 五月婷婷狠狠 | 天天操天天谢 | 碰天天操天天 | 久久dvd | 在线免费色视频 | 久久视频免费在线 | 亚洲欧洲国产精品 | 成人午夜网址 | 成人9ⅰ免费影视网站 | 欧美一二三区播放 | 久久久久免费视频 | 丁香婷婷激情国产高清秒播 | 天天干天天天 | 亚洲国产精久久久久久久 | 精品福利国产 | 九九热精品视频在线播放 | 久久免费看毛片 | 成人a视频在线观看 | 69国产精品视频 | 亚洲aⅴ乱码精品成人区 | 特级片免费看 | 久久污视频 | 狠狠躁日日躁狂躁夜夜躁 | 欧美另类美少妇69xxxx | 国产精品久久艹 | 毛片精品免费在线观看 | 五月婷婷操 | 伊人伊成久久人综合网小说 | 亚洲小视频在线 | av电影不卡 | wwxxxx日本| 成人av播放| 在线观看 国产 | 一本一本久久a久久精品牛牛影视 | 久草在线高清 | 黄色免费视频在线观看 | 日韩电影中文字幕在线 | 国产亲近乱来精品 | 久草影视在线观看 | 又色又爽又黄高潮的免费视频 | 成年人免费电影 | 免费在线播放黄色 | 岛国一区在线 | 久久久久成人精品免费播放动漫 | 免费久久久久久久 | 91精品国产92久久久久 | 亚洲激情视频在线观看 | 日本精品视频在线 | 97在线视频免费播放 | 国产精品一区二区吃奶在线观看 | 韩国三级一区 | 人人射人人澡 | 丝袜+亚洲+另类+欧美+变态 | 成人中文字幕+乱码+中文字幕 | 手机看片99| 国产男女免费完整视频 | 久草在线手机视频 | 中文字幕人成不卡一区 | 超碰97国产在线 | 麻豆91网站 | 激情综合五月婷婷 | 在线免费观看成人 | 激情五月婷婷综合 | 免费在线观看成人小视频 | 欧美激情第28页 | 看片网站黄 | 日韩午夜一级片 | 四虎国产永久在线精品 | 国产打女人屁股调教97 | 色资源在线 | 国产成人精品一区一区一区 | 97精品在线观看 | 亚洲免费av在线播放 | 日本激情视频中文字幕 | 欧美性色综合网站 | 国产精品久久久免费 | 911精品美国片911久久久 | 日韩av片免费在线观看 | 久久久精品网站 | 国产一区在线免费观看 | 激情综合色播五月 | 天天鲁一鲁摸一摸爽一爽 | 免费在线黄 | 免费在线观看中文字幕 | 成人三级黄色 | 婷婷六月综合网 | 国产精品乱码在线 | 97色免费视频 | 日批视频在线播放 | 欧美极品一区二区三区 | 亚州欧美精品 | 国产午夜三级一区二区三桃花影视 | 黄色大全在线观看 | 国产精品久久综合 | 黄网站免费大全入口 | 337p日本大胆噜噜噜噜 | 91亚洲激情| 欧美日韩三级 | 在线观看www. | 国产免费观看高清完整版 | 日日操天天操夜夜操 | 色天天| www麻豆视频 | 日韩av在线小说 | 天天操夜夜操夜夜操 | 免费毛片一区二区三区久久久 | 色综合久久综合 | 久久久久成 | 中文字幕亚洲在线观看 | 久久国内视频 | 免费看v片网站 | 久久国产精品99久久久久久进口 | 欧美一区二区三区特黄 | 91激情在线视频 | av福利电影| 99久久精品费精品 | 久免费 | 97碰碰精品嫩模在线播放 | 五月综合激情网 | 国产精品丝袜久久久久久久不卡 | 日韩二区三区在线 | 国产精品24小时在线观看 | 日韩精品亚洲专区在线观看 | 超碰97人人爱 | 国产午夜三级一区二区三桃花影视 | 色99色 | 国产欧美高清 | 超碰在线免费福利 | 久久久午夜精品福利内容 | 97色涩 | 国产一区久久 | 五月婷婷网站 | 伊色综合久久之综合久久 | 精品国产福利在线 | 午夜精品一区二区三区可下载 | 久久精品国产第一区二区三区 | 日韩中文字幕免费电影 | 国产一区高清在线观看 | 国产91精品一区二区绿帽 | 麻豆精品传媒视频 | 亚洲一区二区91 | 婷婷色中文网 | 国产午夜精品一区二区三区 | 久久精品久久精品久久精品 | 日本久久高清视频 | 激情久久综合网 | 免费日韩一区二区三区 | 亚洲丁香日韩 | 性色av一区二区三区在线观看 | 最新中文字幕视频 | 中文字幕免费高清在线观看 | 九九九九色 | 欧美在线99 | 狠狠色狠狠色合久久伊人 | 日本在线成人 | 国产黄色精品网站 | 日韩视频免费在线观看 | 国产亚洲精品久久久网站好莱 | 免费福利影院 | 日韩在线电影一区 | 久久精品电影网 | 亚洲精品国久久99热 | av中文电影 | 国产高清视频在线播放 | 成年人免费av网站 | 亚洲国产一区二区精品专区 | 婷婷中文字幕综合 | 国产黄色在线网站 | 黄色毛片网站在线观看 | 天天综合视频在线观看 | 日韩电影一区二区在线观看 | 天天射天天射天天射 | 18pao国产成视频永久免费 | 天天性天天草 | 蜜桃av久久久亚洲精品 | 在线观看视频在线 | 国产精品一区二区精品视频免费看 | 日韩黄色中文字幕 | 9999国产| 国产又粗又猛又色又黄网站 | 在线黄网站 | 中文字幕在线有码 | 91九色蝌蚪视频在线 | 色偷偷人人澡久久超碰69 | 808电影| 久久婷婷一区二区三区 | 成人午夜毛片 | 亚洲精品乱码久久久久久 | 亚洲精品乱码久久久久久蜜桃动漫 | 久久黄色片子 | 日韩高清精品一区二区 | 黄色片免费电影 | 日韩高清在线一区二区 | 久久久久观看 | 91污在线观看 | 天堂在线免费视频 | 国产 日韩 欧美 中文 在线播放 | 欧美一级在线观看视频 | 欧美午夜a | 国产精品乱码久久久久 | 国产一区二区久久 | 久久亚洲视频 | 精品a在线 | 日韩在线免费电影 | 欧美伦理一区二区三区 | 精品日本视频 | 草久久av| av成人亚洲 | 日韩中午字幕 | 国产成人精品一区二区在线观看 | 国产视频不卡一区 | 在线一区av| 亚洲资源| 国产一二区精品 | 懂色av一区二区在线播放 | 国产精品大片 | 久久久免费精品 | 96国产精品视频 | 免费三及片 | 中文字幕免费高清在线观看 | 九九久久久久久久久激情 | 日韩欧美在线观看 | 欧美极品少妇xxxxⅹ欧美极品少妇xxxx亚洲精品 | 国产欧美精品在线观看 | 91麻豆精品国产91久久久久 | 日韩欧美网站 | 激情五月***国产精品 | 黄色大片日本 | 精品视频免费 | 在线观看av黄色 | 黄免费在线观看 | 国产91免费观看 | 久久久久久久99 | 日韩av综合网站 | 在线精品在线 | 久草网站| av天天澡天天爽天天av | 在线精品在线 | 久久久久久久久久伊人 | 黄色三级av | 超碰免费久久 | 99视频在线观看视频 | 欧美一区二视频在线免费观看 | 五月天久久激情 | 99色视频| 国产黄色成人av | 日本韩国精品在线 | 最近中文字幕在线 | 国产欧美最新羞羞视频在线观看 | 成人黄色电影在线观看 | 国产精品第一 | av成人动漫在线观看 | 天堂激情网 | 特级黄色片免费看 | zzijzzij亚洲日本少妇熟睡 | 岛国av在线不卡 | 国产午夜三级一区二区三桃花影视 | 免费看91的网站 | 欧美国产日韩激情 | 久草视频资源 | 美女网站在线观看 | 国产在线精品区 | 国产超碰在线观看 | 99se视频在线观看 | 国产精品毛片久久蜜 | 91视频免费网站 | 国产一级特黄电影 | 国产中文字幕在线播放 | 91福利影院在线观看 | 国内精品久久久久久久影视麻豆 | 亚洲精品一区二区三区在线观看 | 欧美成人一二区 | 人人草在线观看 | 午夜免费福利片 | 亚洲精品在线播放视频 | 91高清在线看 | 国产高清成人 | 国产99久久久精品 | 免费午夜视频在线观看 | 中文乱幕日产无线码1区 | 日韩精品一区二区久久 | 免费三级网| 亚洲精品午夜国产va久久成人 | 久久综合视频网 | 69国产精品视频免费观看 | 色在线免费视频 | 玖玖在线免费视频 | 成人在线黄色电影 | 伊人久久婷婷 | 99久久精品无码一区二区毛片 | 国产精品网站一区二区三区 | 日韩免费在线看 | 麻豆免费视频网站 | 色婷婷播放 | 久久久久免费 | 日韩成人精品一区二区 | 久久九九影视网 | 成人黄色免费观看 | 国产又粗又猛又爽 | 91爱爱电影 | 丁香色婷 | 超碰人人超碰 | 日韩www在线 | 在线免费观看视频 | 日日碰夜夜爽 | 欧美久久久久久久久久久久久 | 亚洲精品国产精品国自产 | 免费av视屏 | 免费看片网站91 | 久久av网址 | 久久久亚洲精华液 | 色视频在线观看免费 | 日韩免费观看一区二区 | 麻豆精品在线 | 久久国产视频网 | 久久精品国产精品亚洲精品 | 美女视频黄免费的 | 国产精品第2页 | 开心综合网 | 亚洲成av人电影 | 日本论理电影 | av电影免费看 | 欧美日韩一区二区久久 | 亚洲成av人片在线观看 | 国产日韩精品在线观看 | 国产精品麻豆果冻传媒在线播放 | 精品女同一区二区三区在线观看 | 国产aa免费视频 | 亚洲乱亚洲乱妇 | 亚洲国产偷 | 国产精品久久久久久高潮 | 精品福利国产 | 在线直播av | 亚洲日本韩国一区二区 | 狠狠狠的干 | 日韩高清一 | av网站免费线看精品 | 玖玖爱在线观看 | 日韩免费观看高清 | 午夜少妇 | 国产免费叼嘿网站免费 | 午夜精品一区二区国产 | 国产网站在线免费观看 | 久久久久久久久久久久久影院 | 免费99精品国产自在在线 | 免费观看的av | 三级av在线播放 | 欧美国产日韩在线观看 | 国产成人三级三级三级97 | 91伊人久久大香线蕉蜜芽人口 | 欧美日韩国产精品一区二区亚洲 | 国产福利91精品一区二区三区 | 久久精品99国产精品亚洲最刺激 | 亚洲精品动漫在线 | 国产精品视频app | 一区二区视频在线观看免费 | 日韩三级视频在线观看 | 久久影院亚洲 | 亚洲高清视频在线播放 | 四虎成人精品永久免费av九九 | 国产又粗又猛又色又黄视频 | av中文字幕日韩 | 久久精品欧美视频 | 黄色在线观看免费网站 | 婷婷深爱网| 97人人超碰在线 | 久久久久女人精品毛片九一 | 国产一卡久久电影永久 | 狠狠狠色丁香婷婷综合激情 | 色婷婷色| 亚洲自拍偷拍色图 | 亚洲最大av在线播放 | 久久高清片| 中文字幕在线观看第一页 | 日韩中文字幕在线看 | av在线进入 | 黄色成品视频 | 国产亚洲成人精品 | 麻豆传媒在线视频 | 中文字幕亚洲不卡 | 国产免费黄视频在线观看 | 日韩高清精品一区二区 | 97超级碰 | 欧洲av不卡 | 视频国产在线 | 天天操网| 米奇四色影视 | 黄色日视频 | 成 人 黄 色 视频 免费观看 | 日日爽夜夜操 | 人成午夜视频 | 成人精品一区二区三区中文字幕 | 国产免费xvideos视频入口 | 精品久久一区二区三区 | 在线小视频你懂得 | 夜夜视频欧洲 | 超碰人人av | 在线免费三级 | 99精品电影 | 久久久www成人免费精品张筱雨 | 免费一级特黄毛大片 | 国产成人精品免费在线观看 | 久久精品网站免费观看 | 久久精品香蕉 | 国产一线二线三线性视频 | 999国产精品视频 | 三级黄色免费片 | 一区二区视频在线看 | 中文字幕久久精品一区 | 中文字幕一区二区三区在线观看 | 欧美在线18 | 四虎精品成人免费网站 | 中午字幕在线观看 | 国产高清在线视频 | 亚洲一区二区麻豆 | 中文国产字幕在线观看 | 天天干,天天射,天天操,天天摸 | 国产免费国产 | 在线免费观看的av网站 | 午夜色影院 | 日韩精品免费一区二区三区 | 天天干天天上 | 婷婷精品 | 天天操夜夜爱 | 亚洲视频六区 | 一区二区三区四区精品视频 | 久久女同性恋中文字幕 | 狠狠操操 | 91天堂影院| 精品成人a区在线观看 | 精品久久久久国产免费第一页 | 久久精品xxx | 在线成人免费 | 日韩免费一区二区三区 | av电影 一区二区 | 69人人| 久久无码av一区二区三区电影网 | 成年人免费在线观看网站 | 久久69av | 亚洲综合在线五月天 | 亚洲粉嫩av | 久久成人亚洲欧美电影 | 成年人视频免费在线播放 | 丁香激情综合久久伊人久久 | 久久久久高清毛片一级 | 欧美资源 | 久久精品久久精品久久精品 | av免费成人 | av一区二区在线观看中文字幕 | 超碰大片| 日韩影视在线观看 | 一区二区av | 欧美激情在线网站 | 在线观看中文字幕网站 | 欧美日韩午夜在线 | 国产丝袜美腿在线 | 91理论片午午伦夜理片久久 | 久久99国产精品 | 人人爽久久久噜噜噜电影 | 国产福利午夜 | 亚洲精品毛片一级91精品 | 波多野结衣在线播放视频 | 欧美日韩xxxxx | 黄色av电影在线观看 | 国产精品一区在线观看你懂的 | 在线看片a| www日| 国产精品va在线 | 免费的国产精品 | 91精品国产三级a在线观看 | 国产亚洲欧美精品久久久久久 | 国产成人av免费在线观看 | 狠狠躁日日躁夜夜躁av | 久久99国产精品久久99 | 久久成人人人人精品欧 | 日韩成人精品一区二区 | 国产欧美最新羞羞视频在线观看 | 激情视频免费在线观看 | 激情综合五月 | 米奇影视7777| 久久九九精品久久 | 最近中文字幕免费大全 | 国内精品久久久久影院优 | 日韩视频中文字幕在线观看 | 国产午夜精品久久久久久久久久 | 国产在线色视频 | 日本中文字幕在线电影 | 99在线观看视频 | 成人亚洲免费 | 免费视频黄 | 黄色av电影网 | 久久国产二区 | 手机av在线免费观看 | 97在线看片| 日本深夜福利视频 | 久草免费手机视频 | 国产精品不卡在线观看 | 免费观看十分钟 | 日韩在线视频免费观看 | av中文字幕在线看 | 国产 一区二区三区 在线 | 亚州av网站 | 欧美 激情在线 | 手机av在线免费观看 | 天天躁日日躁狠狠躁av麻豆 | 久久免费成人精品视频 | 欧美另类巨大 | 国产一区欧美二区 | 亚洲欧洲精品久久 | 激情五月激情综合网 | 在线日韩视频 | 国产精品一区二区在线播放 | 日韩视频在线不卡 | 狠狠色狠狠色综合日日92 | 91视频这里只有精品 | 网站在线观看你们懂的 | 日韩xxxbbb | 亚洲精品自拍视频在线观看 | 免费看三级网站 | 久久草在线视频国产 | 91精品国自产在线偷拍蜜桃 | 免费日韩电影 | 亚洲最大成人网4388xx | 久久精品91久久久久久再现 | 久久精品欧美一 | 最新国产一区二区三区 | 欧美精品免费在线 | 伊人电影在线观看 | 久久免费在线视频 | 激情av在线资源 | 日本激情视频中文字幕 | 久久这里只有精品视频首页 | 国产专区在线 | 国内99视频 | 99免费在线播放99久久免费 | 日韩欧美精品在线视频 | 久久久精品网站 | 国产成人a v电影 | 一区二区 不卡 | 国产原创91 | 在线观看视频黄色 | 亚洲尺码电影av久久 | 久久躁日日躁aaaaxxxx | 男女男视频 | 91在线精品秘密一区二区 | 中文字幕日韩免费视频 | 一区二区视频免费在线观看 | 在线观看视频你懂的 | 久久99中文字幕 | 国产视频高清 | 久久精品视频国产 | www.久久色| 激情视频网页 | 欧美一区二区三区激情视频 | 日韩| 国产精品99久久久精品免费观看 | 久久精品99久久久久久2456 | 毛片网站在线观看 | 欧美日韩综合在线观看 | 免费黄色在线网站 | 99久久精品午夜一区二区小说 | 欧美激情综合色 |