Ajax 完整教程 第 1 頁(yè) Ajax 簡(jiǎn)介 Ajax 由 HTML、JavaScript? 技術(shù)、DHTML 和 DOM 組成,這一杰出的方法可以將笨拙的 Web 界面轉(zhuǎn)化成交互性的 Ajax 應(yīng)用程序。本文的作者是一位 Ajax 專家,他演示了這些技術(shù)如何協(xié)同工作 —— 從總體概述到細(xì)節(jié)的討論 —— 使高效的 Web 開(kāi)發(fā)成為現(xiàn)實(shí)。他還揭開(kāi)了 Ajax 核心概念的神秘面紗,包括 XMLHttpRequest 對(duì)象。 五年前,如果不知道 XML,您就是一只無(wú)人重視的丑小鴨。十八個(gè)月前,Ruby 成了關(guān)注的中心,不知道 Ruby 的程序員只能坐冷板凳了。今天,如果想跟上最新的技術(shù)時(shí)尚,那您的目標(biāo)就是 Ajax。 但是,Ajax 不僅僅 是一種時(shí)尚,它是一種構(gòu)建網(wǎng)站的強(qiáng)大方法,而且不像學(xué)習(xí)一種全新的語(yǔ)言那樣困難。 但在詳細(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ù)器上 —— 毫不奇怪,要通過(guò) Web 瀏覽器訪問(wèn)這種應(yīng)用程序。 不過(guò),比這些應(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)性。可以單擊、選擇、輸入、打開(kāi)菜單和子菜單、到處巡游,基本上不需要等待。 另一方面,Web 應(yīng)用程序是最新的潮流,它們提供了在桌面上不能實(shí)現(xiàn)的服務(wù)(比如 Amazon.com 和 eBay)。但是,伴隨著 Web 的強(qiáng)大而出現(xiàn)的是等待,等待服務(wù)器響應(yīng),等待屏幕刷新,等待請(qǐng)求返回和生成新的頁(yè)面。 顯然這樣說(shuō)過(guò)于簡(jiǎn)略了,但基本的概念就是如此。您可能已經(jīng)猜到,Ajax 嘗試建立桌面應(yīng)用程序的功能和交互性,與不斷更新的 Web 應(yīng)用程序之間的橋梁。可以使用像桌面應(yīng)用程序中常見(jiàn)的動(dòng)態(tài)用戶界面和漂亮的控件,不過(guò)是在 Web 應(yīng)用程序中。 還等什么呢?我們來(lái)看看 Ajax 如何將笨拙的 Web 界面轉(zhuǎn)化成能迅速響應(yīng)的 Ajax 應(yīng)用程序吧。 老技術(shù),新技巧 在談到 Ajax 時(shí),實(shí)際上涉及到多種技術(shù),要靈活地運(yùn)用它必須深入了解這些不同的技術(shù)(本系列的頭幾篇文章將分別討論這些技術(shù))。好消息是您可能已經(jīng)非常熟悉其中的大部分技術(shù),更好的是這些技術(shù)都很容易學(xué)習(xí),并不像完整的編程語(yǔ)言(如 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 元素來(lái)標(biāo)記 HTML。 ·文檔對(duì)象模型 DOM 用于(通過(guò) JavaScript 代碼)處理 HTML 結(jié)構(gòu)和(某些情況下)服務(wù)器返回的 XML。 Ajax 的定義 順便說(shuō)一下,Ajax 是 Asynchronous JavaScript and XML(以及 DHTML 等)的縮寫。這個(gè)短語(yǔ)是 Adaptive Path 的 Jesse James Garrett 發(fā)明的(請(qǐng)參閱 參考資料),按照 Jesse 的解釋,這不是 個(gè)首字母縮寫詞。 我們來(lái)進(jìn)一步分析這些技術(shù)的職責(zé)。以后的文章中我將深入討論這些技術(shù),目前只要熟悉這些組件和技術(shù)就可以了。對(duì)這些代碼越熟悉,就越容易從對(duì)這些技術(shù)的零散了解轉(zhuǎn)變到真正把握這些技術(shù)(同時(shí)也真正打開(kāi)了 Web 應(yīng)用程序開(kāi)發(fā)的大門)。 XMLHttpRequest 對(duì)象 要了解的一個(gè)對(duì)象可能對(duì)您來(lái)說(shuō)也是最陌生的,即 XMLHttpRequest。這是一個(gè) JavaScript 對(duì)象,創(chuàng)建該對(duì)象很簡(jiǎn)單,如清單 1 所示。 清單 1. 創(chuàng)建新的 XMLHttpRequest 對(duì)象 <script language="javascript" type="text/javascript"> var xmlHttp = new XMLHttpRequest(); </script> 下一期文章中將進(jìn)一步討論這個(gè)對(duì)象,現(xiàn)在要知道這是處理所有服務(wù)器通信的對(duì)象。繼續(xù)閱讀之前,先停下來(lái)想一想:通過(guò) XMLHttpRequest 對(duì)象與服務(wù)器進(jìn)行對(duì)話的是 JavaScript 技術(shù)。這不是一般的應(yīng)用程序流,這恰恰是 Ajax 的強(qiáng)大功能的來(lái)源。 在一般的 Web 應(yīng)用程序中,用戶填寫表單字段并單擊 Submit 按鈕。然后整個(gè)表單發(fā)送到服務(wù)器,服務(wù)器將它轉(zhuǎn)發(fā)給處理表單的腳本(通常是 PHP 或 Java,也可能是 CGI 進(jìn)程或者類似的東西),腳本執(zhí)行完成后再發(fā)送回全新的頁(yè)面。該頁(yè)面可能是帶有已經(jīng)填充某些數(shù)據(jù)的新表單的 HTML,也可能是確認(rèn)頁(yè)面,或者是具有根據(jù)原來(lái)表單中輸入數(shù)據(jù)選擇的某些選項(xiàng)的頁(yè)面。當(dāng)然,在服務(wù)器上的腳本或程序處理和返回新表單時(shí)用戶必須等待。屏幕變成一片空白,等到服務(wù)器返回?cái)?shù)據(jù)后再重新繪制。這就是交互性差的原因,用戶得不到立即反饋,因此感覺(jué)不同于桌面應(yīng)用程序。 Ajax 基本上就是把 JavaScript 技術(shù)和 XMLHttpRequest 對(duì)象放在 Web 表單和服務(wù)器之間。當(dāng)用戶填寫表單時(shí),數(shù)據(jù)發(fā)送給一些 JavaScript 代碼而不是 直接發(fā)送給服務(wù)器。相反,JavaScript 代碼捕獲表單數(shù)據(jù)并向服務(wù)器發(fā)送請(qǐng)求。同時(shí)用戶屏幕上的表單也不會(huì)閃爍、消失或延遲。換句話說(shuō),JavaScript 代碼在幕后發(fā)送請(qǐng)求,用戶甚至不知道請(qǐng)求的發(fā)出。更好的是,請(qǐng)求是異步發(fā)送的,就是說(shuō) JavaScript 代碼(和用戶)不用等待服務(wù)器的響應(yīng)。因此用戶可以繼續(xù)輸入數(shù)據(jù)、滾動(dòng)屏幕和使用應(yīng)用程序。 然后,服務(wù)器將數(shù)據(jù)返回 JavaScript 代碼(仍然在 Web 表單中),后者決定如何處理這些數(shù)據(jù)。它可以迅速更新表單數(shù)據(jù),讓人感覺(jué)應(yīng)用程序是立即完成的,表單沒(méi)有提交或刷新而用戶得到了新數(shù)據(jù)。JavaScript 代碼甚至可以對(duì)收到的數(shù)據(jù)執(zhí)行某種計(jì)算,再發(fā)送另一個(gè)請(qǐng)求,完全不需要用戶干預(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 代碼就非常簡(jiǎn)單了。事實(shí)上,我們將使用 JavaScript 代碼完成非常基本的任務(wù): ·獲取表單數(shù)據(jù):JavaScript 代碼很容易從 HTML 表單中抽取數(shù)據(jù)并發(fā)送到服務(wù)器。 ·修改表單上的數(shù)據(jù):更新表單也很簡(jiǎn)單,從設(shè)置字段值到迅速替換圖像。 ·解析 HTML 和 XML:使用 JavaScript 代碼操縱 DOM(請(qǐng)參閱 下一節(jié)),處理 HTML 表單服務(wù)器返回的 XML 數(shù)據(jù)的結(jié)構(gòu)。 對(duì)于前兩點(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]; 這里沒(méi)有特別需要注意的地方,真是好極了!您應(yīng)該認(rèn)識(shí)到這里并沒(méi)有非常復(fù)雜的東西。只要掌握了 XMLHttpRequest,Ajax 應(yīng)用程序的其他部分就是如 清單 2 所示的簡(jiǎn)單 JavaScript 代碼了,混合有少量的 HTML。同時(shí),還要用一點(diǎn)兒 DOM,我們就來(lái)看看吧。 以 DOM 結(jié)束 最后還有 DOM,即文檔對(duì)象模型。可能對(duì)有些讀者來(lái)說(shuō) 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)該說(shuō)明如何使用 DOM,或者至少要給出一些示例代碼,但這樣做也可能誤導(dǎo)您。即使不理會(huì) DOM,仍然能深入地探討 Ajax,這也是我準(zhǔn)備采用的方法。以后的文章將再次討論 DOM,現(xiàn)在只要知道可能需要 DOM 就可以了。當(dāng)需要在 JavaScript 代碼和服務(wù)器之間傳遞 XML 和改變 HTML 表單的時(shí)候,我們?cè)偕钊胙芯?DOM。沒(méi)有它也能做一些有趣的工作,因此現(xiàn)在就把 DOM 放到一邊吧。 獲取 Request 對(duì)象 有了上面的基礎(chǔ)知識(shí)后,我們來(lái)看看一些具體的例子。XMLHttpRequest 是 Ajax 應(yīng)用程序的核心,而且對(duì)很多讀者來(lái)說(shuō)可能還比較陌生,我們就從這里開(kāi)始吧。從 清單 1 可以看出,創(chuàng)建和使用這個(gè)對(duì)象非常簡(jiǎn)單,不是嗎?等一等。 還記得幾年前的那些討厭的瀏覽器戰(zhàn)爭(zhēng)嗎?沒(méi)有一樣?xùn)|西在不同的瀏覽器上得到同樣的結(jié)果。不管您是否相信,這些戰(zhàn)爭(zhēng)仍然在繼續(xù),雖然規(guī)模較小。但令人奇怪的是,XMLHttpRequest 成了這場(chǎng)戰(zhàn)爭(zhēng)的犧牲品之一。因此獲得 XMLHttpRequest 對(duì)象可能需要采用不同的方法。下面我將詳細(xì)地進(jìn)行解釋。 使用 Microsoft 瀏覽器 Microsoft 瀏覽器 Internet Explorer 使用 MSXML 解析器處理 XML(可以通過(guò) 參考資料 進(jìn)一步了解 MSXML)。因此如果編寫的 Ajax 應(yīng)用程序要和 Internet Explorer 打交道,那么必須用一種特殊的方式創(chuàng)建對(duì)象。 但并不是這么簡(jiǎn)單。根據(jù) Internet Explorer 中安裝的 JavaScript 技術(shù)版本不同,MSXML 實(shí)際上有兩種不同的版本,因此必須對(duì)這兩種情況分別編寫代碼。請(qǐng)參閱 清單 3,其中的代碼在 Microsoft 瀏覽器上創(chuàng)建了一個(gè) XMLHttpRequest。 清單 3. 在 Microsoft 瀏覽器上創(chuàng)建 XMLHttpRequest 對(duì)象 var xmlHttp = false; try { xmlHttp = new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) { try { xmlHttp = new ActiveXObject("Microsoft.XMLHTTP"); } catch (e2) { xmlHttp = false; } } 您對(duì)這些代碼可能還不完全理解,但沒(méi)有關(guān)系。當(dāng)本系列文章結(jié)束的時(shí)候,您將對(duì) JavaScript 編程、錯(cuò)誤處理、條件編譯等有更深的了解。現(xiàn)在只要牢牢記住其中的兩行代碼: xmlHttp = new ActiveXObject("Msxml2.XMLHTTP"); 和 xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");。 這兩行代碼基本上就是嘗試使用一個(gè)版本的 MSXML 創(chuàng)建對(duì)象,如果失敗則使用另一個(gè)版本創(chuàng)建該對(duì)象。不錯(cuò)吧?如果都不成功,則將 xmlHttp 變量設(shè)為 false,告訴您的代碼出現(xiàn)了問(wèn)題。如果出現(xiàn)這種情況,可能是因?yàn)榘惭b了非 Microsoft 瀏覽器,需要使用不同的代碼。 處理 Mozilla 和非 Microsoft 瀏覽器 如果選擇的瀏覽器不是 Internet Explorer,或者為非 Microsoft 瀏覽器編寫代碼,就需要使用不同的代碼。事實(shí)上就是 清單 1 所示的一行簡(jiǎn)單代碼: var xmlHttp = new XMLHttpRequest object;。 這行簡(jiǎn)單得多的代碼在 Mozilla、Firefox、Safari、Opera 以及基本上所有以任何形式或方式支持 Ajax 的非 Microsoft 瀏覽器中,創(chuàng)建了 XMLHttpRequest 對(duì)象。 結(jié)合起來(lái) 關(guān)鍵是要支持所有 瀏覽器。誰(shuí)愿意編寫一個(gè)只能用于 Internet Explorer 或者非 Microsoft 瀏覽器的應(yīng)用程序呢?或者更糟,要編寫一個(gè)應(yīng)用程序兩次?當(dāng)然不!因此代碼要同時(shí)支持 Internet Explorer 和非 Microsoft 瀏覽器。清單 4 顯示了這樣的代碼。 清單 4. 以支持多種瀏覽器的方式創(chuàng)建 XMLHttpRequest 對(duì)象 /* 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 && typeof XMLHttpRequest != 'undefined') { xmlHttp = new XMLHttpRequest(); } 現(xiàn)在先不管那些注釋掉的奇怪符號(hào),如 @cc_on,這是特殊的 JavaScript 編譯器命令,將在下一期針對(duì) XMLHttpRequest 的文章中詳細(xì)討論。這段代碼的核心分為三步: 1、建立一個(gè)變量 xmlHttp 來(lái)引用即將創(chuàng)建的 XMLHttpRequest 對(duì)象。
2、嘗試在 Microsoft 瀏覽器中創(chuàng)建該對(duì)象: 1)嘗試使用 Msxml2.XMLHTTP 對(duì)象創(chuàng)建它。 2)如果失敗,再嘗試 Microsoft.XMLHTTP 對(duì)象。 2、如果仍然沒(méi)有建立 xmlHttp,則以非 Microsoft 的方式創(chuàng)建該對(duì)象。 最后,xmlHttp 應(yīng)該引用一個(gè)有效的 XMLHttpRequest 對(duì)象,無(wú)論運(yùn)行什么樣的瀏覽器。 關(guān)于安全性的一點(diǎn)說(shuō)明 安全性如何呢?現(xiàn)在瀏覽器允許用戶提高他們的安全等級(jí),關(guān)閉 JavaScript 技術(shù),禁用瀏覽器中的任何選項(xiàng)。在這種情況下,代碼無(wú)論如何都不會(huì)工作。此時(shí)必須適當(dāng)?shù)靥幚韱?wèn)題,這需要單獨(dú)的一篇文章來(lái)討論,要放到以后了(這個(gè)系列夠長(zhǎng)了吧?不用擔(dān)心,讀完之前也許您就掌握了)。現(xiàn)在要編寫一段健壯但不夠完美的代碼,對(duì)于掌握 Ajax 來(lái)說(shuō)就很好了。以后我們還將討論更多的細(xì)節(jié)。 Ajax 世界中的請(qǐng)求/響應(yīng) 現(xiàn)在我們介紹了 Ajax,對(duì) XMLHttpRequest 對(duì)象以及如何創(chuàng)建它也有了基本的了解。如果閱讀得很仔細(xì),您可能已經(jīng)知道與服務(wù)器上的 Web 應(yīng)用程序打交道的是 JavaScript 技術(shù),而不是直接提交給那個(gè)應(yīng)用程序的 HTML 表單。 還缺少什么呢?到底如何使用 XMLHttpRequest。因?yàn)檫@段代碼非常重要,您編寫的每個(gè) Ajax 應(yīng)用程序都要以某種形式使用它,先看看 Ajax 的基本請(qǐng)求/響應(yīng)模型是什么樣吧。 發(fā)出請(qǐng)求 您已經(jīng)有了一個(gè)嶄新的 XMLHttpRequest 對(duì)象,現(xiàn)在讓它干點(diǎn)活兒吧。首先需要一個(gè) Web 頁(yè)面能夠調(diào)用的 JavaScript 方法(比如當(dāng)用戶輸入文本或者從菜單中選擇一項(xiàng)時(shí))。接下來(lái)就是在所有 Ajax 應(yīng)用程序中基本都雷同的流程: 1、從 Web 表單中獲取需要的數(shù)據(jù)。 2、建立要連接的 URL。 3、打開(kāi)到服務(wù)器的連接。 4、設(shè)置服務(wù)器在完成后要運(yùn)行的函數(shù)。 5、發(fā)送請(qǐng)求。 清單 5 中的示例 Ajax 方法就是按照這個(gè)順序組織的: 清單 5. 發(fā)出 Ajax 請(qǐng)求 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); } 其中大部分代碼意義都很明確。開(kāi)始的代碼使用基本 JavaScript 代碼獲取幾個(gè)表單字段的值。然后設(shè)置一個(gè) PHP 腳本作為鏈接的目標(biāo)。要注意腳本 URL 的指定方式,city 和 state(來(lái)自表單)使用簡(jiǎn)單的 GET 參數(shù)附加在 URL 之后。 然后打開(kāi)一個(gè)連接,這是您第一次看到使用 XMLHttpRequest。其中指定了連接方法(GET)和要連接的 URL。最后一個(gè)參數(shù)如果設(shè)為 true,那么將請(qǐng)求一個(gè)異步連接(這就是 Ajax 的由來(lái))。如果使用 false,那么代碼發(fā)出請(qǐng)求后將等待服務(wù)器返回的響應(yīng)。如果設(shè)為 true,當(dāng)服務(wù)器在后臺(tái)處理請(qǐng)求的時(shí)候用戶仍然可以使用表單(甚至調(diào)用其他 JavaScript 方法)。 xmlHttp(要記住,這是 XMLHttpRequest 對(duì)象實(shí)例)的 onreadystatechange 屬性可以告訴服務(wù)器在運(yùn)行完成 后(可能要用五分鐘或者五個(gè)小時(shí))做什么。因?yàn)榇a沒(méi)有等待服務(wù)器,必須讓服務(wù)器知道怎么做以便您能作出響應(yīng)。在這個(gè)示例中,如果服務(wù)器處理完了請(qǐng)求,一個(gè)特殊的名為 updatePage() 的方法將被觸發(fā)。 最后,使用值 null 調(diào)用 send()。因?yàn)橐呀?jīng)在請(qǐng)求 URL 中添加了要發(fā)送給服務(wù)器的數(shù)據(jù)(city 和 state),所以請(qǐng)求中不需要發(fā)送任何數(shù)據(jù)。這樣就發(fā)出了請(qǐng)求,服務(wù)器按照您的要求工作。 如果沒(méi)有發(fā)現(xiàn)任何新鮮的東西,您應(yīng)該體會(huì)到這是多么簡(jiǎn)單明了!除了牢牢記住 Ajax 的異步特性外,這些內(nèi)容都相當(dāng)簡(jiǎn)單。應(yīng)該感激 Ajax 使您能夠?qū)P木帉懫恋膽?yīng)用程序和界面,而不用擔(dān)心復(fù)雜的 HTTP 請(qǐng)求/響應(yīng)代碼。 清單 5 中的代碼說(shuō)明了 Ajax 的易用性。數(shù)據(jù)是簡(jiǎn)單的文本,可以作為請(qǐng)求 URL 的一部分。用 GET 而不是更復(fù)雜的 POST 發(fā)送請(qǐng)求。沒(méi)有 XML 和要添加的內(nèi)容頭部,請(qǐng)求體中沒(méi)有要發(fā)送的數(shù)據(jù);換句話說(shuō),這就是 Ajax 的烏托邦。 不用擔(dān)心,隨著本系列文章的展開(kāi),事情會(huì)變得越來(lái)越復(fù)雜。您將看到如何發(fā)送 POST 請(qǐng)求、如何設(shè)置請(qǐng)求頭部和內(nèi)容類型、如何在消息中編碼 XML、如何增加請(qǐng)求的安全性,可以做的工作還有很多!暫時(shí)先不用管那些難點(diǎn),掌握好基本的東西就行了,很快我們就會(huì)建立一整套的 Ajax 工具庫(kù)。 處理響應(yīng) 現(xiàn)在要面對(duì)服務(wù)器的響應(yīng)了。現(xiàn)在只要知道兩點(diǎn): ·什么也不要做,直到 xmlHttp.readyState 屬性的值等于 4。 ·服務(wù)器將把響應(yīng)填充到 xmlHttp.responseText 屬性中。 其中的第一點(diǎn),即就緒狀態(tài),將在下一篇文章中詳細(xì)討論,您將進(jìn)一步了解 HTTP 請(qǐng)求的階段,可能比您設(shè)想的還多。現(xiàn)在只要檢查一個(gè)特定的值(4)就可以了(下一期文章中還有更多的值要介紹)。第二點(diǎn),使用 xmlHttp.responseText 屬性獲得服務(wù)器的響應(yīng),這很簡(jiǎn)單。清單 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)了,而用戶沒(méi)有按任何按鈕!這就是前面所說(shuō)的桌面應(yīng)用程序的感覺(jué)。快速響應(yīng)、動(dòng)態(tài)感受等等,這些都只因?yàn)橛辛诵⌒〉囊欢?Ajax 代碼。 細(xì)心的讀者可能注意到 zipCode 是一個(gè)普通的文本字段。一旦服務(wù)器返回 ZIP 編碼,updatePage() 方法就用城市/州的 ZIP 編碼設(shè)置那個(gè)字段的值,用戶就可以改寫該值。這樣做有兩個(gè)原因:保持例子簡(jiǎn)單,說(shuō)明有時(shí)候可能希望 用戶能夠修改服務(wù)器返回的數(shù)據(jù)。要記住這兩點(diǎn),它們對(duì)于好的用戶界面設(shè)計(jì)來(lái)說(shuō)很重要。 連接 Web 表單 還有什么呢?實(shí)際上沒(méi)有多少了。一個(gè) JavaScript 方法捕捉用戶輸入表單的信息并將其發(fā)送到服務(wù)器,另一個(gè) JavaScript 方法監(jiān)聽(tīng)和處理響應(yīng),并在響應(yīng)返回時(shí)設(shè)置字段的值。所有這些實(shí)際上都依賴于調(diào)用 第一個(gè) JavaScript 方法,它啟動(dòng)了整個(gè)過(guò)程。最明顯的辦法是在 HTML 表單中增加一個(gè)按鈕,但這是 2001 年的辦法,您不這樣認(rèn)為嗎?還是像 清單 7 這樣利用 JavaScript 技術(shù)吧。 清單 7. 啟動(dòng)一個(gè) Ajax 過(guò)程 <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> 如果感覺(jué)這像是一段相當(dāng)普通的代碼,那就對(duì)了,正是如此!當(dāng)用戶在 city 或 state 字段中輸入新的值時(shí),callServer() 方法就被觸發(fā),于是 Ajax 開(kāi)始運(yùn)行了。有點(diǎn)兒明白怎么回事了吧?好,就是如此! 結(jié)束語(yǔ) 現(xiàn)在您可能已經(jīng)準(zhǔn)備開(kāi)始編寫第一個(gè) Ajax 應(yīng)用程序了,至少也希望認(rèn)真讀一下 參考資料 中的那些文章了吧?但可以首先從這些應(yīng)用程序如何工作的基本概念開(kāi)始,對(duì) XMLHttpRequest 對(duì)象有基本的了解。在下一期文章中,您將掌握這個(gè)對(duì)象,學(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ù)器對(duì)它的請(qǐng)求作出響應(yīng)。會(huì)遇到什么樣的問(wèn)題?會(huì)進(jìn)入什么樣的領(lǐng)域?考慮到這種新的方法,編程的時(shí)候應(yīng)如何改變表單的設(shè)計(jì)? 如果在這些問(wèn)題上花一點(diǎn)兒時(shí)間,與簡(jiǎn)單地剪切/粘貼某些代碼到您根本不理解的應(yīng)用程序中相比,收益會(huì)更多。在下一期文章中,我們將把這些概念付諸實(shí)踐,詳細(xì)介紹使應(yīng)用程序按照這種方式工作所需要的代碼。因此,現(xiàn)在先享受一下 Ajax 所帶來(lái)的可能性吧。 第 2 頁(yè) 使用 JavaScript 和 Ajax 發(fā)出異步請(qǐng)求
多數(shù) Web 應(yīng)用程序都使用請(qǐng)求/響應(yīng)模型從服務(wù)器上獲得完整的 HTML 頁(yè)面。常常是點(diǎn)擊一個(gè)按鈕,等待服務(wù)器響應(yīng),再點(diǎn)擊另一個(gè)按鈕,然后再等待,這樣一個(gè)反復(fù)的過(guò)程。有了 Ajax 和 XMLHttpRequest 對(duì)象,就可以使用不必讓用戶等待服務(wù)器響應(yīng)的請(qǐng)求/響應(yīng)模型了。本文中,Brett McLaughlin 介紹了如何創(chuàng)建能夠適應(yīng)不同瀏覽器的 XMLHttpRequest 實(shí)例,建立和發(fā)送請(qǐng)求,并響應(yīng)服務(wù)器。 本系列的上一期文章(請(qǐng)參閱 參考資料 中的鏈接),我們介紹了 Ajax 應(yīng)用程序,考察了推動(dòng) Ajax 應(yīng)用程序的基本概念。其中的核心是很多您可能已經(jīng)了解的技術(shù):JavaScript、HTML 和 XHTML、一點(diǎn)動(dòng)態(tài) HTML 以及 DOM(文檔對(duì)象模型)。本文將放大其中的一點(diǎn),把目光放到具體的 Ajax 細(xì)節(jié)上。 本文中,您將開(kāi)始接觸最基本和基礎(chǔ)性的有關(guān) Ajax 的全部對(duì)象和編程方法:XMLHttpRequest 對(duì)象。該對(duì)象實(shí)際上僅僅是一個(gè)跨越所有 Ajax 應(yīng)用程序的公共線程,您可能已經(jīng)預(yù)料到,只有徹底理解該對(duì)象才能充分發(fā)揮編程的潛力。事實(shí)上,有時(shí)您會(huì)發(fā)現(xiàn),要正確地使用 XMLHttpRequest,顯然不能 使用 XMLHttpRequest。這到底是怎么回事呢? Web 2.0 一瞥 在深入研究代碼之前首先看看最近的觀點(diǎn) —— 一定要十分清楚 Web 2.0 這個(gè)概念。聽(tīng)到 Web 2.0 這個(gè)詞的時(shí)候,應(yīng)該首先問(wèn)一問(wèn) “Web 1.0 是什么?” 雖然很少聽(tīng)人提到 Web 1.0,實(shí)際上它指的就是具有完全不同的請(qǐng)求和響應(yīng)模型的傳統(tǒng) Web。比如,到 Amazon.com 網(wǎng)站上點(diǎn)擊一個(gè)按鈕或者輸入搜索項(xiàng)。就會(huì)對(duì)服務(wù)器發(fā)送一個(gè)請(qǐng)求,然后響應(yīng)再返回到瀏覽器。該請(qǐng)求不僅僅是圖書(shū)和書(shū)目列表,而是另一個(gè)完整的 HTML 頁(yè)面。因此當(dāng) Web 瀏覽器用新的 HTML 頁(yè)面重繪時(shí),可能會(huì)看到閃爍或抖動(dòng)。事實(shí)上,通過(guò)看到的每個(gè)新頁(yè)面可以清晰地看到請(qǐng)求和響應(yīng)。 Web 2.0(在很大程度上)消除了這種看得見(jiàn)的往復(fù)交互。比如訪問(wèn) Google Maps 或 Flickr 這樣的站點(diǎn)(到這些支持 Web 2.0 和 Ajax 站點(diǎn)的鏈接請(qǐng)參閱 參考資料)。比如在 Google Maps 上,您可以拖動(dòng)地圖,放大和縮小,只有很少的重繪操作。當(dāng)然這里仍然有請(qǐng)求和響應(yīng),只不過(guò)都藏到了幕后。作為用戶,體驗(yàn)更加舒適,感覺(jué)很像桌面應(yīng)用程序。這種新的感受和范型就是當(dāng)有人提到 Web 2.0 時(shí)您所體會(huì)到的。 需要關(guān)心的是如何使這些新的交互成為可能。顯然,仍然需要發(fā)出請(qǐng)求和接收響應(yīng),但正是針對(duì)每次請(qǐng)求/響應(yīng)交互的 HTML 重繪造成了緩慢、笨拙的 Web 交互的感受。因此很清楚,我們需要一種方法使發(fā)送的請(qǐng)求和接收的響應(yīng)只 包含需要的數(shù)據(jù)而不是整個(gè) HTML 頁(yè)面。惟一需要獲得整個(gè)新 HTML 頁(yè)面的時(shí)候就是希望用戶看到 新頁(yè)面的時(shí)候。 但多數(shù)交互都是在已有頁(yè)面上增加細(xì)節(jié)、修改主體文本或者覆蓋原有數(shù)據(jù)。這些情況下,Ajax 和 Web 2.0 方法允許在不 更新整個(gè) HTML 頁(yè)面的情況下發(fā)送和接收數(shù)據(jù)。對(duì)于那些經(jīng)常上網(wǎng)的人,這種能力可以讓您的應(yīng)用程序感覺(jué)更快、響應(yīng)更及時(shí),讓他們不時(shí)地光顧您的網(wǎng)站。 XMLHttpRequest 簡(jiǎn)介 要真正實(shí)現(xiàn)這種絢麗的奇跡,必須非常熟悉一個(gè) JavaScript 對(duì)象,即 XMLHttpRequest。這個(gè)小小的對(duì)象實(shí)際上已經(jīng)在幾種瀏覽器中存在一段時(shí)間了,它是本專欄今后幾個(gè)月中要介紹的 Web 2.0、Ajax 和大部分其他內(nèi)容的核心。為了讓您快速地大體了解它,下面給出將要用于該對(duì)象的很少的幾個(gè) 方法和屬性。 ·open():建立到服務(wù)器的新請(qǐng)求。 ·send():向服務(wù)器發(fā)送請(qǐng)求。 ·abort():退出當(dāng)前請(qǐng)求。 ·readyState:提供當(dāng)前 HTML 的就緒狀態(tài)。 ·responseText:服務(wù)器返回的請(qǐng)求響應(yīng)文本。 如果不了解這些(或者其中的任何 一個(gè)),您也不用擔(dān)心,后面幾篇文章中我們將介紹每個(gè)方法和屬性。現(xiàn)在應(yīng)該 了解的是,明確用 XMLHttpRequest 做什么。要注意這些方法和屬性都與發(fā)送請(qǐng)求及處理響應(yīng)有關(guān)。事實(shí)上,如果看到 XMLHttpRequest 的所有方法和屬性,就會(huì)發(fā)現(xiàn)它們都 與非常簡(jiǎn)單的請(qǐng)求/響應(yīng)模型有關(guān)。顯然,我們不會(huì)遇到特別新的 GUI 對(duì)象或者創(chuàng)建用戶交互的某種超極神秘的方法,我們將使用非常簡(jiǎn)單的請(qǐng)求和非常簡(jiǎn)單的響應(yīng)。聽(tīng)起來(lái)似乎沒(méi)有多少吸引力,但是用好該對(duì)象可以徹底改變您的應(yīng)用程序。 簡(jiǎn)單的 new 首先需要?jiǎng)?chuàng)建一個(gè)新變量并賦給它一個(gè) XMLHttpRequest 對(duì)象實(shí)例。這在 JavaScript 中很簡(jiǎn)單,只要對(duì)該對(duì)象名使用 new 關(guān)鍵字即可,如 清單 1 所示。 清單 1. 創(chuàng)建新的 XMLHttpRequest 對(duì)象 <script language="javascript" type="text/javascript"> var request = new XMLHttpRequest(); </script> 不難吧?記住,JavaScript 不要求指定變量類型,因此不需要像 清單 2 那樣做(在 Java 語(yǔ)言中可能需要這樣)。 清單 2. 創(chuàng)建 XMLHttpRequest 的 Java 偽代碼 XMLHttpRequest request = new XMLHttpRequest(); 因此在 JavaScript 中用 var 創(chuàng)建一個(gè)變量,給它一個(gè)名字(如 “request”),然后賦給它一個(gè)新的 XMLHttpRequest 實(shí)例。此后就可以在函數(shù)中使用該對(duì)象了。 錯(cuò)誤處理 在實(shí)際上各種事情都可能出錯(cuò),而上面的代碼沒(méi)有提供任何錯(cuò)誤處理。較好的辦法是創(chuàng)建該對(duì)象,并在出現(xiàn)問(wèn)題時(shí)優(yōu)雅地退出。比如,任何較早的瀏覽器(不論您是否相信,仍然有人在使用老版本的 Netscape Navigator)都不支持 XMLHttpRequest,您需要讓這些用戶知道有些地方出了問(wèn)題。清單 3 說(shuō)明如何創(chuàng)建該對(duì)象,以便在出現(xiàn)問(wè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 作為判定條件,它表示還沒(méi)有創(chuàng)建 XMLHttpRequest 對(duì)象。 2、增加 try/catch 塊: 1)嘗試創(chuàng)建 XMLHttpRequest 對(duì)象。 2)如果失敗(catch (failed))則保證 request 的值仍然為 false。 3、檢查 request 是否仍為 false(如果一切正常就不會(huì)是 false)。 4、如果出現(xiàn)問(wèn)題(request 是 false)則使用 JavaScript 警告通知用戶出現(xiàn)了問(wèn)題。 代碼非常簡(jiǎn)單,對(duì)大多數(shù) JavaScript 和 Web 開(kāi)發(fā)人員來(lái)說(shuō),真正理解它要比讀寫代碼花更長(zhǎng)的時(shí)間。現(xiàn)在已經(jīng)得到了一段帶有錯(cuò)誤檢查的 XMLHttpRequest 對(duì)象創(chuàng)建代碼,還可以告訴您哪兒出了問(wèn)題。 應(yīng)付 Microsoft 看起來(lái)似乎一切良好,至少在用 Internet Explorer 試驗(yàn)這些代碼之前是這樣的。如果這樣試驗(yàn)的話,就會(huì)看到 圖 1 所示的糟糕情形。 圖 1. Internet Explorer 報(bào)告錯(cuò)誤 ![]() 顯然有什么地方不對(duì)勁,而 Internet Explorer 很難說(shuō)是一種過(guò)時(shí)的瀏覽器,因?yàn)槿澜缬?70% 在使用 Internet Explorer。換句話說(shuō),如果不支持 Microsoft 和 Internet Explorer 就不會(huì)受到 Web 世界的歡迎!因此我們需要采用不同的方法處理 Microsoft 瀏覽器。 經(jīng)驗(yàn)證發(fā)現(xiàn) Microsoft 支持 Ajax,但是其 XMLHttpRequest 版本有不同的稱呼。事實(shí)上,它將其稱為幾種 不同的東西。如果使用較新版本的 Internet Explorer,則需要使用對(duì)象 Msxml2.XMLHTTP,而較老版本的 Internet Explorer 則使用 Microsoft.XMLHTTP。我們需要支持這兩種對(duì)象類型(同時(shí)還要支持非 Microsoft 瀏覽器)。請(qǐng)看看 清單 4,它在前述代碼的基礎(chǔ)上增加了對(duì) Microsoft 的支持。 Microsoft 參與了嗎? 關(guān)于 Ajax 和 Microsoft 對(duì)該領(lǐng)域不斷增長(zhǎng)的興趣和參與已經(jīng)有很多文章進(jìn)行了介紹。事實(shí)上,據(jù)說(shuō) Microsoft 最新版本的 Internet Explorer —— version 7.0,將在 2006 年下半年推出 —— 將開(kāi)始直接支持 XMLHttpRequest,讓您使用 new 關(guān)鍵字代替所有的 Msxml2.XMLHTTP 創(chuàng)建代碼。但不要太激動(dòng),仍然需要支持舊的瀏覽器,因此跨瀏覽器代碼不會(huì)很快消失。 清單 4. 增加對(duì) 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> 很容易被這些花括號(hào)迷住了眼睛,因此下面分別介紹每一步: 1、創(chuàng)建一個(gè)新變量 request 并賦值 false。使用 false 作為判斷條件,它表示還沒(méi)有創(chuàng)建 XMLHttpRequest 對(duì)象。 2、增加 try/catch 塊: 1)嘗試創(chuàng)建 XMLHttpRequest 對(duì)象。 2)如果失敗(catch (trymicrosoft)): 1>嘗試使用較新版本的 Microsoft 瀏覽器創(chuàng)建 Microsoft 兼容的對(duì)象(Msxml2.XMLHTTP)。 2> 如果失敗(catch (othermicrosoft))嘗試使用較老版本的 Microsoft 瀏覽器創(chuàng)建 Microsoft 兼容的對(duì)象(Microsoft.XMLHTTP)。 2)如果失敗(catch (failed))則保證 request 的值仍然為 false。 3、檢查 request 是否仍然為 false(如果一切順利就不會(huì)是 false)。 4、如果出現(xiàn)問(wèn)題(request 是 false)則使用 JavaScript 警告通知用戶出現(xiàn)了問(wèn)題。 這樣修改代碼之后再使用 Internet Explorer 試驗(yàn),就應(yīng)該看到已經(jīng)創(chuàng)建的表單(沒(méi)有錯(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。就是說(shuō)代碼是在頁(yè)面顯示給用戶之前的某個(gè)時(shí)候運(yùn)行。(雖然根據(jù)規(guī)范不能完全精確地 知道這些代碼何時(shí)運(yùn)行對(duì)瀏覽器有什么影響,但是可以保證這些代碼在用戶能夠與頁(yè)面交互之前運(yùn)行。)這也是多數(shù) Ajax 程序員創(chuàng)建 XMLHttpRequest 對(duì)象的一般方式。 就是說(shuō),也可以像 清單 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> 此代碼惟一的問(wèn)題是推遲了錯(cuò)誤通知,這也是多數(shù) Ajax 程序員不采用這一方法的原因。假設(shè)一個(gè)復(fù)雜的表單有 10 或 15 個(gè)字段、選擇框等,當(dāng)用戶在第 14 個(gè)字段(按照表單順序從上到下)輸入文本時(shí)要激活某些 Ajax 代碼。這時(shí)候運(yùn)行 getCustomerInfo() 嘗試創(chuàng)建一個(gè) XMLHttpRequest 對(duì)象,但(對(duì)于本例來(lái)說(shuō))失敗了。然后向用戶顯示一條警告,明確地告訴他們不能使用該應(yīng)用程序。但用戶已經(jīng)花費(fèi)了很多時(shí)間在表單中輸入數(shù)據(jù)!這是非常令人討厭的,而討厭顯然不會(huì)吸引用戶再次訪問(wèn)您的網(wǎng)站。 如果使用靜態(tài) JavaScript,用戶在點(diǎn)擊頁(yè)面的時(shí)候很快就會(huì)看到錯(cuò)誤信息。這樣也很煩人,是不是?可能令用戶錯(cuò)誤地認(rèn)為您的 Web 應(yīng)用程序不能在他的瀏覽器上運(yùn)行。不過(guò),當(dāng)然要比他們花費(fèi)了 10 分鐘輸入信息之后再顯示同樣的錯(cuò)誤要好。因此,我建議編寫靜態(tài)的代碼,讓用戶盡可能早地發(fā)現(xiàn)問(wèn)題。 用 XMLHttpRequest 發(fā)送請(qǐng)求 得到請(qǐng)求對(duì)象之后就可以進(jìn)入請(qǐng)求/響應(yīng)循環(huán)了。記住,XMLHttpRequest 惟一的目的是讓您發(fā)送請(qǐng)求和接收響應(yīng)。其他一切都是 JavaScript、CSS 或頁(yè)面中其他代碼的工作:改變用戶界面、切換圖像、解釋服務(wù)器返回的數(shù)據(jù)。準(zhǔn)備好 XMLHttpRequest 之后,就可以向服務(wù)器發(fā)送請(qǐng)求了。 歡迎使用沙箱 Ajax 采用一種沙箱安全模型。因此,Ajax 代碼(具體來(lái)說(shuō)就是 XMLHttpRequest 對(duì)象)只能對(duì)所在的同一個(gè)域發(fā)送請(qǐng)求。以后的文章中將進(jìn)一步介紹安全和 Ajax,現(xiàn)在只要知道在本地機(jī)器上運(yùn)行的代碼只能對(duì)本地機(jī)器上的服務(wù)器端腳本發(fā)送請(qǐng)求。如果讓 Ajax 代碼在 http://www./ 上運(yùn)行,則必須 http://www./ 中運(yùn)行的腳本發(fā)送請(qǐng)求。 設(shè)置服務(wù)器 URL 首先要確定連接的服務(wù)器的 URL。這并不是 Ajax 的特殊要求,但仍然是建立連接所必需的,顯然現(xiàn)在您應(yīng)該知道如何構(gòu)造 URL 了。多數(shù)應(yīng)用程序中都會(huì)結(jié)合一些靜態(tài)數(shù)據(jù)和用戶處理的表單中的數(shù)據(jù)來(lái)構(gòu)造該 URL。比如,清單 7 中的 JavaScript 代碼獲取電話號(hào)碼字段的值并用其構(gòu)造 URL。 清單 7. 建立請(qǐng)求 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> 這里沒(méi)有難懂的地方。首先,代碼創(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)用戶輸入電話號(hào)碼或者改變電話號(hào)碼時(shí),將觸發(fā) 清單 8 所示的 getCustomerInfo() 方法。該方法取得電話號(hào)碼并構(gòu)造存儲(chǔ)在 url 變量中的 URL 字符串。記住,由于 Ajax 代碼是沙箱型的,因而只能連接到同一個(gè)域,實(shí)際上 URL 中不需要域名。該例中的腳本名為 /cgi-local/lookupCustomer.php。最后,電話號(hào)碼作為 GET 參數(shù)附加到該腳本中:"phone=" + escape(phone)。 如果以前沒(méi)用見(jiàn)過(guò) escape() 方法,它用于轉(zhuǎn)義不能用明文正確發(fā)送的任何字符。比如,電話號(hào)碼中的空格將被轉(zhuǎn)換成字符 %20,從而能夠在 URL 中傳遞這些字符。 可以根據(jù)需要添加任意多個(gè)參數(shù)。比如,如果需要增加另一個(gè)參數(shù),只需要將其附加到 URL 中并用 “與”(&)字符分開(kāi) [第一個(gè)參數(shù)用問(wèn)號(hào)(?)和腳本名分開(kāi)]。 打開(kāi)請(qǐng)求 有了要連接的 URL 后就可以配置請(qǐng)求了。可以用 XMLHttpRequest 對(duì)象的 open() 方法來(lái)完成。該方法有五個(gè)參數(shù): request-type:發(fā)送請(qǐng)求的類型。典型的值是 GET 或 POST,但也可以發(fā)送 HEAD 請(qǐng)求。 url:要連接的 URL。 asynch:如果希望使用異步連接則為 true,否則為 false。該參數(shù)是可選的,默認(rèn)為 true。 username:如果需要身份驗(yàn)證,則可以在此指定用戶名。該可選參數(shù)沒(méi)有默認(rèn)值。 password:如果需要身份驗(yàn)證,則可以在此指定口令。該可選參數(shù)沒(méi)有默認(rèn)值。 open() 是打開(kāi)嗎? Internet 開(kāi)發(fā)人員對(duì) open() 方法到底做什么沒(méi)有達(dá)成一致。但它實(shí)際上并不是 打開(kāi)一個(gè)請(qǐng)求。如果監(jiān)控 XHTML/Ajax 頁(yè)面及其連接腳本之間的網(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)持明確指定請(qǐng)求是異步的還是同步的更容易理解。 將這些結(jié)合起來(lái),通常會(huì)得到 清單 9 所示的一行代碼。 清單 9. 打開(kāi)請(qǐng)求 function getCustomerInfo() { var phone = document.getElementById("phone").value; var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone); request.open("GET", url, true); } 一旦設(shè)置好了 URL,其他就簡(jiǎn)單了。多數(shù)請(qǐng)求使用 GET 就夠了(后面的文章中將看到需要使用 POST 的情況),再加上 URL,這就是使用 open() 方法需要的全部?jī)?nèi)容了。 挑戰(zhàn)異步性 本系列的后面一篇文章中,我將用很多時(shí)間編寫和使用異步代碼,但是您應(yīng)該明白為什么 open() 的最后一個(gè)參數(shù)這么重要。在一般的請(qǐng)求/響應(yīng)模型中,比如 Web 1.0,客戶機(jī)(瀏覽器或者本地機(jī)器上運(yùn)行的代碼)向服務(wù)器發(fā)出請(qǐng)求。該請(qǐng)求是同步的,換句話說(shuō),客戶機(jī)等待服務(wù)器的響應(yīng)。當(dāng)客戶機(jī)等待的時(shí)候,至少會(huì)用某種形式通知您在等待: ·沙漏(特別是 Windows 上)。 ·旋轉(zhuǎn)的皮球(通常在 Mac 機(jī)器上)。 ·應(yīng)用程序基本上凍結(jié)了,然后過(guò)一段時(shí)間光標(biāo)變化了。 這正是 Web 應(yīng)用程序讓人感到笨拙或緩慢的原因 —— 缺乏真正的交互性。按下按鈕時(shí),應(yīng)用程序?qū)嶋H上變得不能使用,直到剛剛觸發(fā)的請(qǐng)求得到響應(yīng)。如果請(qǐng)求需要大量服務(wù)器處理,那么等待的時(shí)間可能很長(zhǎng)(至少在這個(gè)多處理器、DSL 沒(méi)有等待的世界中是如此)。 而異步請(qǐng)求不 等待服務(wù)器響應(yīng)。發(fā)送請(qǐng)求后應(yīng)用程序繼續(xù)運(yùn)行。用戶仍然可以在 Web 表單中輸入數(shù)據(jù),甚至離開(kāi)表單。沒(méi)有旋轉(zhuǎn)的皮球或者沙漏,應(yīng)用程序也沒(méi)有明顯的凍結(jié)。服務(wù)器悄悄地響應(yīng)請(qǐng)求,完成后告訴原來(lái)的請(qǐng)求者工作已經(jīng)結(jié)束(具體的辦法很快就會(huì)看到)。結(jié)果是,應(yīng)用程序感覺(jué)不 那么遲鈍或者緩慢,而是響應(yīng)迅速、交互性強(qiáng),感覺(jué)快多了。這僅僅是 Web 2.0 的一部分,但它是很重要的一部分。所有老套的 GUI 組件和 Web 設(shè)計(jì)范型都不能克服緩慢、同步的請(qǐng)求/響應(yīng)模型。 發(fā)送請(qǐng)求 一旦用 open() 配置好之后,就可以發(fā)送請(qǐng)求了。幸運(yùn)的是,發(fā)送請(qǐng)求的方法的名稱要比 open() 適當(dāng),它就是 send()。 send() 只有一個(gè)參數(shù),就是要發(fā)送的內(nèi)容。但是在考慮這個(gè)方法之前,回想一下前面已經(jīng)通過(guò) URL 本身發(fā)送過(guò)數(shù)據(jù)了: var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone); 雖然可以使用 send() 發(fā)送數(shù)據(jù),但也能通過(guò) URL 本身發(fā)送數(shù)據(jù)。事實(shí)上,GET 請(qǐng)求(在典型的 Ajax 應(yīng)用中大約占 80%)中,用 URL 發(fā)送數(shù)據(jù)要容易得多。如果需要發(fā)送安全信息或 XML,可能要考慮使用 send() 發(fā)送內(nèi)容(本系列的后續(xù)文章中將討論安全數(shù)據(jù)和 XML 消息)。如果不需要通過(guò) send() 傳遞數(shù)據(jù),則只要傳遞 null 作為該方法的參數(shù)即可。因此您會(huì)發(fā)現(xiàn)在本文中的例子中只需要這樣發(fā)送請(qǐng)求(參見(jiàn) 清單 10)。 清單 10. 發(fā)送請(qǐng)求 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)鍵字建立了異步請(qǐng)求。但是除此之外,這些代碼與用 Java servlet 及 JSP、PHP 或 Perl 編程沒(méi)有什么兩樣。那么 Ajax 和 Web 2.0 最大的秘密是什么呢?秘密就在于 XMLHttpRequest 的一個(gè)簡(jiǎn)單屬性 onreadystatechange。 首先一定要理解這些代碼中的流程(如果需要請(qǐng)回顧 清單 10)。建立其請(qǐng)求然后發(fā)出請(qǐng)求。此外,因?yàn)槭钱惒秸?qǐng)求,所以 JavaScript 方法(例子中的 getCustomerInfo())不會(huì)等待服務(wù)器。因此代碼將繼續(xù)執(zhí)行,就是說(shuō),將退出該方法而把控制返回給表單。用戶可以繼續(xù)輸入信息,應(yīng)用程序不會(huì)等待服務(wù)器。 這就提出了一個(gè)有趣的問(wèn)題:服務(wù)器完成了請(qǐng)求之后會(huì)發(fā)生什么?答案是什么也不發(fā)生,至少對(duì)現(xiàn)在的代碼而言如此!顯然這樣不行,因此服務(wù)器在完成通過(guò) XMLHttpRequest 發(fā)送給它的請(qǐng)求處理之后需要某種指示說(shuō)明怎么做。 在 JavaScript 中引用函數(shù): JavaScript 是一種弱類型的語(yǔ)言,可以用變量引用任何東西。因此如果聲明了一個(gè)函數(shù) updatePage(),JavaScript 也將該函數(shù)名看作是一個(gè)變量。換句話說(shuō),可用變量名 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ā)送請(qǐng)求之前必須設(shè)置該屬性,這樣服務(wù)器在回答完成請(qǐng)求之后才能查看該屬性。現(xiàn)在剩下的就只有編寫 updatePage() 方法了,這是本文最后一節(jié)要討論的重點(diǎn)。 處理服務(wù)器響應(yīng) 發(fā)送請(qǐng)求,用戶高興地使用 Web 表單(同時(shí)服務(wù)器在處理請(qǐng)求),而現(xiàn)在服務(wù)器完成了請(qǐng)求處理。服務(wù)器查看 onreadystatechange 屬性確定要調(diào)用的方法。除此以外,可以將您的應(yīng)用程序看作其他應(yīng)用程序一樣,無(wú)論是否異步。換句話說(shuō),不一定要采取特殊的動(dòng)作編寫響應(yīng)服務(wù)器的方法,只需要改變表單,讓用戶訪問(wèn)另一個(gè) URL 或者做響應(yīng)服務(wù)器需要的任何事情。這一節(jié)我們重點(diǎn)討論對(duì)服務(wù)器的響應(yīng)和一種典型的動(dòng)作 —— 即時(shí)改變用戶看到的表單中的一部分。 回調(diào)和 Ajax 現(xiàn)在我們已經(jīng)看到如何告訴服務(wù)器完成后應(yīng)該做什么:將 XMLHttpRequest 對(duì)象的 onreadystatechange 屬性設(shè)置為要運(yùn)行的函數(shù)名。這樣,當(dāng)服務(wù)器處理完請(qǐng)求后就會(huì)自動(dòng)調(diào)用該函數(shù)。也不需要擔(dān)心該函數(shù)的任何參數(shù)。我們從一個(gè)簡(jiǎn)單的方法開(kāi)始,如 清單 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ā)出一些簡(jiǎn)單的警告,告訴您服務(wù)器什么時(shí)候完成了任務(wù)。在自己的網(wǎng)頁(yè)中試驗(yàn)這些代碼,然后在瀏覽器中打開(kāi)(如果希望查看該例中的 XHTML,請(qǐng)參閱 清單 8)。輸入電話號(hào)碼然后離開(kāi)該字段,將看到一個(gè)彈出的警告窗口(如 圖 3 所示),但是點(diǎn)擊 OK 又出現(xiàn)了…… 圖 3. 彈出警告的 Ajax 代碼 ![]() 根據(jù)瀏覽器的不同,在表單停止彈出警告之前會(huì)看到兩次、三次甚至四次警告。這是怎么回事呢?原來(lái)我們還沒(méi)有考慮 HTTP 就緒狀態(tài),這是請(qǐng)求/響應(yīng)循環(huán)中的一個(gè)重要部分。 HTTP 就緒狀態(tài) 前面提到,服務(wù)器在完成請(qǐng)求之后會(huì)在 XMLHttpRequest 的 onreadystatechange 屬性中查找要調(diào)用的方法。這是真的,但還不完整。事實(shí)上,每當(dāng) HTTP 就緒狀態(tài)改變時(shí)它都會(huì)調(diào)用該方法。這意味著什么呢?首先必須理解 HTTP 就緒狀態(tài)。 HTTP 就緒狀態(tài)表示請(qǐng)求的狀態(tài)或情形。它用于確定該請(qǐng)求是否已經(jīng)開(kāi)始、是否得到了響應(yīng)或者請(qǐng)求/響應(yīng)模型是否已經(jīng)完成。它還可以幫助確定讀取服務(wù)器提供的響應(yīng)文本或數(shù)據(jù)是否安全。在 Ajax 應(yīng)用程序中需要了解五種就緒狀態(tài): ·0:請(qǐng)求沒(méi)有發(fā)出(在調(diào)用 open() 之前)。 ·1:請(qǐng)求已經(jīng)建立但還沒(méi)有發(fā)出(調(diào)用 send() 之前)。 ·2:請(qǐng)求已經(jīng)發(fā)出正在處理之中(這里通常可以從響應(yīng)得到內(nèi)容頭部)。 ·3:請(qǐng)求已經(jīng)處理,響應(yīng)中通常有部分?jǐn)?shù)據(jù)可用,但是服務(wù)器還沒(méi)有完成響應(yīng)。 ·4:響應(yīng)已完成,可以訪問(wèn)服務(wù)器響應(yīng)并使用它。 與大多數(shù)跨瀏覽器問(wèn)題一樣,這些就緒狀態(tài)的使用也不盡一致。您也許期望任務(wù)就緒狀態(tài)從 0 到 1、2、3 再到 4,但實(shí)際上很少是這種情況。一些瀏覽器從不報(bào)告 0 或 1 而直接從 2 開(kāi)始,然后是 3 和 4。其他瀏覽器則報(bào)告所有的狀態(tài)。還有一些則多次報(bào)告就緒狀態(tài) 1。在上一節(jié)中看到,服務(wù)器多次調(diào)用 updatePage(),每次調(diào)用都會(huì)彈出警告框 —— 可能和預(yù)期的不同! 對(duì)于 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 中的代碼看起來(lái)似乎不錯(cuò),但是還有一個(gè)問(wèn)題 —— 如果服務(wù)器響應(yīng)請(qǐng)求并完成了處理但是報(bào)告了一個(gè)錯(cuò)誤怎么辦?要知道,服務(wù)器端代碼應(yīng)該明白它是由 Ajax、JSP、普通 HTML 表單或其他類型的代碼調(diào)用的,但只能使用傳統(tǒng)的 Web 專用方法報(bào)告信息。而在 Web 世界中,HTTP 代碼可以處理請(qǐng)求中可能發(fā)生的各種問(wèn)題。 比方說(shuō),您肯定遇到過(guò)輸入了錯(cuò)誤的 URL 請(qǐng)求而得到 404 錯(cuò)誤碼的情形,它表示該頁(yè)面不存在。這僅僅是 HTTP 請(qǐng)求能夠收到的眾多錯(cuò)誤碼中的一種(完整的狀態(tài)碼列表請(qǐng)參閱 參考資料 中的鏈接)。表示所訪問(wèn)數(shù)據(jù)受到保護(hù)或者禁止訪問(wèn)的 403 和 401 也很常見(jiàn)。無(wú)論哪種情況,這些錯(cuò)誤碼都是從完成的響應(yīng) 得到的。換句話說(shuō),服務(wù)器履行了請(qǐng)求(即 HTTP 就緒狀態(tài)是 4)但是沒(méi)有返回客戶機(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ò)誤或者其他有問(wèn)題的信息)。因此還要在回調(diào)方法中增加狀態(tài)檢查,如 清單 14 所示。 清單 14. 檢查 HTTP 狀態(tài)碼 function updatePage() { if (request.readyState == 4) if (request.status == 200) alert("Server is done!"); } 為了增加更健壯的錯(cuò)誤處理并盡量避免過(guò)于復(fù)雜,可以增加一兩個(gè)狀態(tài)碼檢查,請(qǐng)看一看 清單 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ì)看到警告信息說(shuō)明要求的 URL 不存在 —— 好極了!很難處理所有的錯(cuò)誤條件,但是這一小小的改變能夠涵蓋典型 Web 應(yīng)用程序中 80% 的問(wèn)題。 讀取響應(yīng)文本 現(xiàn)在可以確保請(qǐng)求已經(jīng)處理完成(通過(guò)就緒狀態(tài)),服務(wù)器給出了正常的響應(yīng)(通過(guò)狀態(tài)碼),最后我們可以處理服務(wù)器返回的數(shù)據(jù)了。返回的數(shù)據(jù)保存在 XMLHttpRequest 對(duì)象的 responseText 屬性中。 關(guān)于 responseText 中的文本內(nèi)容,比如格式和長(zhǎng)度,有意保持含糊。這樣服務(wù)器就可以將文本設(shè)置成任何內(nèi)容。比方說(shuō),一種腳本可能返回逗號(hào)分隔的值,另一種則使用管道符(即 | 字符)分隔的值,還有一種則返回長(zhǎng)文本字符串。何去何從由服務(wù)器決定。 在本文使用的例子中,服務(wù)器返回客戶的上一個(gè)訂單和客戶地址,中間用管道符分開(kāi)。然后使用訂單和地址設(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() 方法從管道符分開(kāi)。得到的數(shù)組放到 response 中。數(shù)組中的第一個(gè)值 —— 上一個(gè)訂單 —— 用 response[0] 訪問(wèn),被設(shè)置為 ID 為 “order” 的字段的值。第二個(gè)值 response[1],即客戶地址,則需要更多一點(diǎn)處理。因?yàn)榈刂分械男杏靡话愕男蟹指舴ā癨n”字符)分隔,代碼中需要用 XHTML 風(fēng)格的行分隔符 <br /> 來(lái)代替。替換過(guò)程使用 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)和處理普通文本有很大不同,涉及到解析、文檔對(duì)象模型(DOM)和其他一些問(wèn)題。后面的文章中將進(jìn)一步介紹 XML。但是因?yàn)?responseXML 通常和 responseText 一起討論,這里有必要提一提。對(duì)于很多簡(jiǎn)單的 Ajax 應(yīng)用程序 responseText 就夠了,但是您很快就會(huì)看到通過(guò) Ajax 應(yīng)用程序也能很好地處理 XML。 結(jié)束語(yǔ) 您可能對(duì) XMLHttpRequest 感到有點(diǎn)厭倦了,我很少看到一整篇文章討論一個(gè)對(duì)象,特別是這種簡(jiǎn)單的對(duì)象。但是您將在使用 Ajax 編寫的每個(gè)頁(yè)面和應(yīng)用程序中反復(fù)使用該對(duì)象。坦白地說(shuō),關(guān)于 XMLHttpRequest 還真有一些可說(shuō)的內(nèi)容。下一期文章中將介紹如何在請(qǐng)求中使用 POST 及 GET,來(lái)設(shè)置請(qǐng)求中的內(nèi)容頭部和從服務(wù)器響應(yīng)讀取內(nèi)容頭部,理解如何在請(qǐng)求/響應(yīng)模型中編碼請(qǐng)求和處理 XML。 再往后我們將介紹常見(jiàn) Ajax 工具箱。這些工具箱實(shí)際上隱藏了本文所述的很多細(xì)節(jié),使得 Ajax 編程更容易。您也許會(huì)想,既然有這么多工具箱為何還要對(duì)底層的細(xì)節(jié)編碼。答案是,如果不知道應(yīng)用程序在做什么,就很難發(fā)現(xiàn)應(yīng)用程序中的問(wèn)題。 因此不要忽略這些細(xì)節(jié)或者簡(jiǎn)單地瀏覽一下,如果便捷華麗的工具箱出現(xiàn)了錯(cuò)誤,您就不必?fù)项^或者發(fā)送郵件請(qǐng)求支持了。如果了解如何直接使用 XMLHttpRequest,就會(huì)發(fā)現(xiàn)很容易調(diào)試和解決最奇怪的問(wèn)題。只有讓其解決您的問(wèn)題,工具箱才是好東西。 因此請(qǐng)熟悉 XMLHttpRequest 吧。事實(shí)上,如果您有使用工具箱的 Ajax 代碼,可以嘗試使用 XMLHttpRequest 對(duì)象及其屬性和方法重新改寫。這是一種不錯(cuò)的練習(xí),可以幫助您更好地理解其中的原理。 下一期文章中將進(jìn)一步討論該對(duì)象,探討它的一些更有趣的屬性(如 responseXML),以及如何使用 POST 請(qǐng)求和以不同的格式發(fā)送數(shù)據(jù)。請(qǐng)開(kāi)始編寫代碼吧,一個(gè)月后我們?cè)倮^續(xù)討論。 第 3 頁(yè) Ajax 中的高級(jí)請(qǐng)求和響應(yīng)
對(duì)于很多 Web 開(kāi)發(fā)人員來(lái)說(shuō),只需要生成簡(jiǎn)單的請(qǐng)求并接收簡(jiǎn)單的響應(yīng)即可;但是對(duì)于希望掌握 Ajax 的開(kāi)發(fā)人員來(lái)說(shuō),必須要全面理解 HTTP 狀態(tài)代碼、就緒狀態(tài)和 XMLHttpRequest 對(duì)象。在本文中,Brett McLaughlin 將向您介紹各種狀態(tài)代碼,并展示瀏覽器如何對(duì)其進(jìn)行處理,本文還給出了在 Ajax 中使用的比較少見(jiàn)的 HTTP 請(qǐng)求。 在本系列的 上篇文章 中,我們將詳細(xì)介紹 XMLHttpRequest 對(duì)象,它是 Ajax 應(yīng)用程序的中心,負(fù)責(zé)處理服務(wù)器端應(yīng)用程序和腳本的請(qǐng)求,并處理從服務(wù)器端組件返回的數(shù)據(jù)。由于所有的 Ajax 應(yīng)用程序都要使用 XMLHttpRequest 對(duì)象,因此您可能會(huì)希望熟悉這個(gè)對(duì)象,從而能夠讓 Ajax 執(zhí)行得更好。 在本文中,我將在上一篇文章的基礎(chǔ)上重點(diǎn)介紹這個(gè)請(qǐng)求對(duì)象的 3 個(gè)關(guān)鍵部分的內(nèi)容: ·HTTP 就緒狀態(tài) ·HTTP 狀態(tài)代碼 ·可以生成的請(qǐng)求類型 這三部分內(nèi)容都是在構(gòu)造一個(gè)請(qǐng)求時(shí)所要考慮的因素;但是介紹這些主題的內(nèi)容太少了。然而,如果您不僅僅是想了解 Ajax 編程的常識(shí),而是希望了解更多內(nèi)容,就需要熟悉就緒狀態(tài)、狀態(tài)代碼和請(qǐng)求本身的內(nèi)容。當(dāng)應(yīng)用程序出現(xiàn)問(wèn)題時(shí) —— 這種問(wèn)題總是存在 —— 那么如果能夠正確理解就緒狀態(tài)、如何生成一個(gè) HEAD 請(qǐng)求或者 400 的狀態(tài)代碼的確切含義,就可以在 5 分鐘內(nèi)調(diào)試出問(wèn)題,而不是在各種挫折和困惑中度過(guò) 5 個(gè)小時(shí)。 下面讓我們首先來(lái)看一下 HTTP 就緒狀態(tài)。 深入了解 HTTP 就緒狀態(tài) 您應(yīng)該還記得在上一篇文章中 XMLHttpRequest 對(duì)象有一個(gè)名為 readyState 的屬性。這個(gè)屬性確保服務(wù)器已經(jīng)完成了一個(gè)請(qǐng)求,通常會(huì)使用一個(gè)回調(diào)函數(shù)從服務(wù)器中讀出數(shù)據(jù)來(lái)更新 Web 表單或頁(yè)面的內(nèi)容。清單 1 給出了一個(gè)簡(jiǎn)單的例子(這也是本系列的上一篇文章中的一個(gè)例子 —— 請(qǐng)參見(jiàn) 參考資料)。 XMLHttpRequest 或 XMLHttp:換名玫瑰 Microsoft? 和 Internet Explorer 使用了一個(gè)名為 XMLHttp 的對(duì)象,而不是 XMLHttpRequest 對(duì)象,而 Mozilla、Opera、Safari 和 大部分非 Microsoft 瀏覽器都使用的是后者。為了簡(jiǎn)單性起見(jiàn),我將這兩個(gè)對(duì)象都簡(jiǎn)單地稱為 XMLHttpRequest。這既符合我們?cè)?Web 上看到的情況,又符合 Microsoft 在 Internet Explorer 7.0 中使用 XMLHttpRequest 作為請(qǐng)求對(duì)象的意圖。(有關(guān)這個(gè)問(wèn)題的更多內(nèi)容,請(qǐng)參見(jiàn) 第 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)最常見(jiàn)(也是最簡(jiǎn)單)的用法。正如您從數(shù)字 "4" 中可以看出的一樣,還有其他幾個(gè)就緒狀態(tài)(您在上一篇文章中也看到過(guò)這個(gè)清單 —— 請(qǐng)參見(jiàn) 參考資料): ·0:請(qǐng)求未初始化(還沒(méi)有調(diào)用 open())。 ·1:請(qǐng)求已經(jīng)建立,但是還沒(méi)有發(fā)送(還沒(méi)有調(diào)用 send())。 ·2:請(qǐng)求已發(fā)送,正在處理中(通常現(xiàn)在可以從響應(yīng)中獲取內(nèi)容頭)。 ·3:請(qǐng)求在處理中;通常響應(yīng)中已有部分?jǐn)?shù)據(jù)可用了,但是服務(wù)器還沒(méi)有完成響應(yīng)的生成。 ·4:響應(yīng)已完成;您可以獲取并使用服務(wù)器的響應(yīng)了。 如果您希望不僅僅是了解 Ajax 編程的基本知識(shí),那么就不但需要知道這些狀態(tài),了解這些狀態(tài)是何時(shí)出現(xiàn)的,以及如何來(lái)使用這些狀態(tài)。首先,您需要學(xué)習(xí)在每種就緒狀態(tài)下可能碰到的是哪種請(qǐng)求狀態(tài)。不幸的是,這一點(diǎn)并不直觀,而且會(huì)涉及幾種特殊的情況。 隱秘就緒狀態(tài) 第一種就緒狀態(tài)的特點(diǎn)是 readyState 屬性為 0(readyState == 0),表示未初始化狀態(tài)。一旦對(duì)請(qǐng)求對(duì)象調(diào)用 open() 之后,這個(gè)屬性就被設(shè)置為 1。由于您通常都是在一對(duì)請(qǐng)求進(jìn)行初始化之后就立即調(diào)用 open(),因此很少會(huì)看到 readyState == 0 的狀態(tài)。另外,未初始化的就緒狀態(tài)在實(shí)際的應(yīng)用程序中是沒(méi)有真正的用處的。 不過(guò)為了滿足我們的興趣,請(qǐng)參見(jiàn) 清單 2 的內(nèi)容,其中顯示了如何在 readyState 被設(shè)置為 0 時(shí)來(lái)獲取這種就緒狀態(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è)簡(jiǎn)單的例子中,getSalesData() 是 Web 頁(yè)面調(diào)用來(lái)啟動(dòng)請(qǐng)求(例如點(diǎn)擊一個(gè)按鈕時(shí))所使用的函數(shù)。注意您必須在調(diào)用 open()之前 來(lái)查看就緒狀態(tài)。圖 1 給出了運(yùn)行這個(gè)應(yīng)用程序的結(jié)果。 圖 1. 就緒狀態(tài) 0 ![]() 顯然,這并不能為您帶來(lái)多少好處;需要確保 尚未 調(diào)用 open() 函數(shù)的情況很少。在大部分 Ajax 編程的真實(shí)情況中,這種就緒狀態(tài)的唯一用法就是使用相同的 XMLHttpRequest 對(duì)象在多個(gè)函數(shù)之間生成多個(gè)請(qǐng)求。在這種(不常見(jiàn)的)情況中,您可能會(huì)在生成新請(qǐng)求之前希望確保請(qǐng)求對(duì)象是處于未初始化狀態(tài)(readyState == 0)。這實(shí)際上是要確保另外一個(gè)函數(shù)沒(méi)有同時(shí)使用這個(gè)對(duì)象。 查看正在處理的請(qǐng)求的就緒狀態(tài) 除了 0 就緒狀態(tài)之外,請(qǐng)求對(duì)象還需要依次經(jīng)歷典型的請(qǐng)求和響應(yīng)的其他幾種就緒狀態(tài),最后才以就緒狀態(tài) 4 的形式結(jié)束。這就是為什么您在大部分回調(diào)函數(shù)中都可以看到 if (request.readyState == 4) 這行代碼;它確保服務(wù)器已經(jīng)完成對(duì)請(qǐng)求的處理,現(xiàn)在可以安全地更新 Web 頁(yè)面或根據(jù)從服務(wù)器返回來(lái)的數(shù)據(jù)來(lái)進(jìn)行操作了。 要查看這種狀態(tài)發(fā)生的過(guò)程非常簡(jiǎn)單。如果就緒狀態(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ù)都使用相同的請(qǐng)求對(duì)象時(shí),您需要檢查就緒狀態(tài) 0 來(lái)確保這個(gè)請(qǐng)求對(duì)象沒(méi)有正在使用,這種機(jī)制會(huì)產(chǎn)生問(wèn)題。由于 readyState == 4 表示一個(gè)已完成的請(qǐng)求,因此您經(jīng)常會(huì)發(fā)現(xiàn)那些目前沒(méi)在使用的處于就緒狀態(tài)的請(qǐng)求對(duì)象仍然被設(shè)置成了 4 —— 這是因?yàn)閺姆?wù)器返回來(lái)的數(shù)據(jù)已經(jīng)使用過(guò)了,但是從它們被設(shè)置為就緒狀態(tài)之后就沒(méi)有進(jìn)行任何變化。有一個(gè)函數(shù) abort() 會(huì)重新設(shè)置請(qǐng)求對(duì)象,但是這個(gè)函數(shù)卻不是真正為了這個(gè)目的而使用的。如果您 必須 使用多個(gè)函數(shù),最好是為每個(gè)函數(shù)都創(chuàng)建并使用一個(gè)函數(shù),而不是在多個(gè)函數(shù)之間共享相同的對(duì)象。 清單 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 頁(yè)面中調(diào)用這個(gè)函數(shù),并讓它向服務(wù)器端的組件發(fā)送一個(gè)請(qǐng)求(例如 清單 2 給出的函數(shù),或本系列文章的第 1 部分和第 2 部分中給出的例子)。確保在建立請(qǐng)求時(shí),將回調(diào)函數(shù)設(shè)置為 updatePage();要實(shí)現(xiàn)這種設(shè)置,可以將請(qǐng)求對(duì)象的 onreadystatechange 屬性設(shè)置為 updatePage()。 這段代碼就是 onreadystatechange 意義的一個(gè)確切展示 —— 每次請(qǐng)求的就緒狀態(tài)發(fā)生變化時(shí),就調(diào)用 updatePage(),然后我們就可以看到一個(gè)警告了。圖 2 給出了一個(gè)調(diào)用這個(gè)函數(shù)的例子,其中就緒狀態(tài)為 1。 圖 2. 就緒狀態(tài) 1 ![]() 您可以自己嘗試運(yùn)行這段代碼。將其放入 Web 頁(yè)面中,然后激活事件處理程序(單擊按鈕,在域之間按 tab 鍵切換焦點(diǎn),或者使用設(shè)置的任何方法來(lái)觸發(fā)請(qǐng)求)。這個(gè)回調(diào)函數(shù)會(huì)運(yùn)行多次 —— 每次就緒狀態(tài)都會(huì)改變 —— 您可以看到每個(gè)就緒狀態(tài)的警告。這是跟蹤請(qǐng)求所經(jīng)歷的各個(gè)階段的最好方法。 瀏覽器的不一致性 在對(duì)這個(gè)過(guò)程有一個(gè)基本的了解之后,請(qǐng)?jiān)囍鴱膸讉€(gè)不同的瀏覽器中訪問(wèn)您的頁(yè)面。您應(yīng)該會(huì)注意到各個(gè)瀏覽器如何處理這些就緒狀態(tài)并不一致。例如,在 Firefox 1.5 中,您會(huì)看到以下就緒狀態(tài): ·1 ·2 ·3 ·4 這并不奇怪,因?yàn)槊總€(gè)請(qǐng)求狀態(tài)都在這里表示出來(lái)了。然而,如果您使用 Safari 來(lái)訪問(wèn)相同的應(yīng)用程序,就應(yīng)該看到 —— 或者看不到 —— 一些有趣的事情。下面是在 Safari 2.0.1 中看到的狀態(tài): ·2 ·3 ·4 Safari 實(shí)際上把第一個(gè)就緒狀態(tài)給丟棄了,也并沒(méi)有什么明顯的原因說(shuō)明為什么要這樣做;不過(guò)這就是 Safari 的工作方式。這還說(shuō)明了一個(gè)重要的問(wèn)題:盡管在使用服務(wù)器上的數(shù)據(jù)之前確保請(qǐng)求的狀態(tài)為 4 是一個(gè)好主意,但是依賴于每個(gè)過(guò)渡期就緒狀態(tài)編寫的代碼的確會(huì)在不同的瀏覽器上得到不同的結(jié)果。 例如,在使用 Opera 8.5 時(shí),所顯示的就緒狀態(tài)情況就更加糟糕了: ·3 ·4 最后,Internet Explorer 會(huì)顯示如下?tīng)顟B(tài): ·1 ·2 ·3 ·4 如果您碰到請(qǐng)求方面的問(wèn)題,這就是用來(lái)發(fā)現(xiàn)問(wèn)題的 首要之處。最好的方式是在 Internet Explorer 和 Firefox 都進(jìn)行一下測(cè)試 —— 您會(huì)看到所有這 4 種狀態(tài),并可以檢查請(qǐng)求的每個(gè)狀態(tài)所處的情況。 接下來(lái)我們?cè)賮?lái)看一下響應(yīng)端的情況。 顯微鏡下的響應(yīng)數(shù)據(jù) 一旦我們理解在請(qǐng)求過(guò)程中發(fā)生的各個(gè)就緒狀態(tài)之后,接下來(lái)就可以來(lái)看一下 XMLHttpRequest 對(duì)象的另外一個(gè)方面了 —— responseText 屬性。回想一下在上一篇文章中我們介紹過(guò)的內(nèi)容,就可以知道這個(gè)屬性用來(lái)從服務(wù)器上獲取數(shù)據(jù)。一旦服務(wù)器完成對(duì)請(qǐng)求的處理之后,就可以將響應(yīng)請(qǐng)求數(shù)據(jù)所需要的任何數(shù)據(jù)放到請(qǐng)求的 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)簡(jiǎn)單;清單 4 稍微有點(diǎn)復(fù)雜,但是它們?cè)陂_(kāi)始時(shí)都要檢查就緒狀態(tài),并獲取 responseText 屬性的值。 查看請(qǐng)求的響應(yīng)文本 與就緒狀態(tài)類似,responseText 屬性的值在整個(gè)請(qǐng)求的生命周期中也會(huì)發(fā)生變化。要查看這種變化,請(qǐng)使用如 清單 5 所示的代碼來(lái)測(cè)試請(qǐng)求的響應(yīng)文本,以及它們的就緒狀態(tài)。 清單 5. 測(cè)試 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)在在瀏覽器中打開(kāi) Web 應(yīng)用程序,并激活您的請(qǐng)求。要更好地看到這段代碼的效果,請(qǐng)使用 Firefox 或 Internet Explorer,因?yàn)檫@兩個(gè)瀏覽器都可以報(bào)告出請(qǐng)求過(guò)程中所有可能的就緒狀態(tài)。例如在就緒狀態(tài) 2 中,就沒(méi)有定義 responseText (請(qǐng)參見(jiàn) 圖 3);如果 JavaScript 控制臺(tái)也已經(jīng)打開(kāi)了,您就會(huì)看到一個(gè)錯(cuò)誤。 圖 3. 就緒狀態(tài)為 2 的響應(yīng)文本 ![]() 不過(guò)在就緒狀態(tài) 3 中,服務(wù)器已經(jīng)在 responseText 屬性中放上了一個(gè)值,至少在這個(gè)例子中是這樣(請(qǐng)參見(jiàn) 圖 4)。 圖 4. 就緒狀態(tài)為 3 的響應(yīng)文本 ![]() 您會(huì)看到就緒狀態(tài)為 3 的響應(yīng)在每個(gè)腳本、每個(gè)服務(wù)器甚至每個(gè)瀏覽器上都是不一樣的。不過(guò),這在調(diào)試應(yīng)用程序中依然是非常有用的。 獲取安全數(shù)據(jù) 所有的文檔和規(guī)范都強(qiáng)調(diào),只有在就緒狀態(tài)為 4 時(shí)數(shù)據(jù)才可以安全使用。相信我,當(dāng)就緒狀態(tài)為 3 時(shí),您很少能找到無(wú)法從 responseText 屬性獲取數(shù)據(jù)的情況。然而,在應(yīng)用程序中將自己的邏輯依賴于就緒狀態(tài) 3 可不是什么好主意 —— 一旦您編寫了依賴于就緒狀態(tài) 3 的完整數(shù)據(jù)的的代碼,幾乎就要自己來(lái)負(fù)責(zé)當(dāng)時(shí)的數(shù)據(jù)不完整問(wèn)題了。 比較好的做法是向用戶提供一些反饋,說(shuō)明在處于就緒狀態(tài) 3 時(shí),很快就會(huì)有響應(yīng)了。盡管使用 alert() 之類的函數(shù)顯然不是什么好主意 —— 使用 Ajax 然后使用一個(gè)警告對(duì)話框來(lái)阻塞用戶顯然是錯(cuò)誤的 —— 不過(guò)您可以在就緒狀態(tài)發(fā)生變化時(shí)更新表單或頁(yè)面中的域。例如,對(duì)于就緒狀態(tài) 1 來(lái)說(shuō)要將進(jìn)度指示器的寬度設(shè)置為 25%,對(duì)于就緒狀態(tài) 2 來(lái)說(shuō)要將進(jìn)度指示器的寬度設(shè)置為 50%,對(duì)于就緒狀態(tài) 3 來(lái)說(shuō)要將進(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 上則沒(méi)有第一個(gè)(1)。由于這個(gè)原因,我將這段代碼留作練習(xí),而沒(méi)有在本文中包括進(jìn)來(lái)。 現(xiàn)在應(yīng)該來(lái)看一下?tīng)顟B(tài)代碼了。 深入了解 HTTP 狀態(tài)代碼 有了就緒狀態(tài)和您在 Ajax 編程技術(shù)中學(xué)習(xí)到的服務(wù)器的響應(yīng),您就可以為 Ajax 應(yīng)用程序添加另外一級(jí)復(fù)雜性了 —— 這要使用 HTTP 狀態(tài)代碼。這些代碼對(duì)于 Ajax 來(lái)說(shuō)并沒(méi)有什么新鮮。從 Web 出現(xiàn)以來(lái),它們就已經(jīng)存在了。在 Web 瀏覽器中您可能已經(jīng)看到過(guò)幾個(gè)狀態(tài)代碼: ·401:未經(jīng)授權(quán) ·403:禁止 ·404:沒(méi)找到 您可以找到更多的狀態(tài)代碼(完整清單請(qǐng)參見(jiàn) 參考資料)。要為 Ajax 應(yīng)用程序另外添加一層控制和響應(yīng)(以及更為健壯的錯(cuò)誤處理)機(jī)制,您需要適當(dāng)?shù)夭榭凑?qǐng)求和響應(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 />"); } } 這對(duì)于 Ajax 編程來(lái)說(shuō)證明是一種短視而錯(cuò)誤的方法。如果腳本需要認(rèn)證,而請(qǐng)求卻沒(méi)有提供有效的證書(shū),那么服務(wù)器就會(huì)返回諸如 403 或 401 之類的錯(cuò)誤代碼。然而,由于服務(wù)器對(duì)請(qǐng)求進(jìn)行了應(yīng)答,因此就緒狀態(tài)就被設(shè)置為 4(即使應(yīng)答并不是請(qǐng)求所期望的也是如此)。最終,用戶沒(méi)有獲得有效數(shù)據(jù),當(dāng) JavaScript 試圖使用不存在的服務(wù)器數(shù)據(jù)時(shí)就可能會(huì)出現(xiàn)嚴(yán)重的錯(cuò)誤。 它花費(fèi)了最小的努力來(lái)確保服務(wù)器不但完成了一個(gè)請(qǐng)求,而且還返回了一個(gè) “一切良好” 的狀態(tài)代碼。這個(gè)代碼是 "200",它是通過(guò) XMLHttpRequest 對(duì)象的 status 屬性來(lái)報(bào)告的。為了確保服務(wù)器不但完成了一個(gè)請(qǐng)求,而且還報(bào)告了一個(gè) OK 狀態(tài),請(qǐng)?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); } } 通過(guò)添加這幾行代碼,您就可以確認(rèn)是否存在問(wèn)題,用戶會(huì)看到一個(gè)有用的錯(cuò)誤消息,而不僅僅是看到一個(gè)由斷章取義的數(shù)據(jù)所構(gòu)成的頁(yè)面,而沒(méi)有任何解釋。 重定向和重新路由 在深入介紹有關(guān)錯(cuò)誤的內(nèi)容之前,我們有必要來(lái)討論一下有關(guān)一個(gè)在使用 Ajax 時(shí) 并不需要 關(guān)心的問(wèn)題 —— 重定向。在 HTTP 狀態(tài)代碼中,這是 300 系列的狀態(tài)代碼,包括: ·301:永久移動(dòng) ·302:找到(請(qǐng)求被重新定向到另外一個(gè) URL/URI 上) ·305:使用代理(請(qǐng)求必須使用一個(gè)代理來(lái)訪問(wèn)所請(qǐng)求的資源) Ajax 程序員可能并不太關(guān)心有關(guān)重定向的問(wèn)題,這是由于兩方面的原因: ·首先,Ajax 應(yīng)用程序通常都是為一個(gè)特定的服務(wù)器端腳本、servlet 或應(yīng)用程序而編寫的。對(duì)于那些您看不到就消失了的組件來(lái)說(shuō),Ajax 程序員就不太清楚了。因此有時(shí)您會(huì)知道資源已經(jīng)移動(dòng)了(因?yàn)槟苿?dòng)了它,或者通過(guò)某種手段移動(dòng)了它),接下來(lái)要修改請(qǐng)求中的 URL,并且不會(huì)再碰到這種結(jié)果了。 更為重要的一個(gè)原因是:Ajax 應(yīng)用程序和請(qǐng)求都是封裝在沙盒中的。這就意味著提供生成 Ajax 請(qǐng)求的 Web 頁(yè)面的域必須是對(duì)這些請(qǐng)求進(jìn)行響應(yīng)的域。因此 ebay.com 所提供的 Web 頁(yè)面就不能對(duì)一個(gè)在 amazon.com 上運(yùn)行的腳本生成一個(gè) Ajax 風(fēng)格的請(qǐng)求;在 ibm.com 上的 Ajax 應(yīng)用程序也無(wú)法對(duì)在 netbeans.org 上運(yùn)行的 servlets 發(fā)出請(qǐng)求。 ·結(jié)果是您的請(qǐng)求無(wú)法重定向到其他服務(wù)器上,而不會(huì)產(chǎn)生安全性錯(cuò)誤。在這些情況中,您根本就不會(huì)得到狀態(tài)代碼。通常在調(diào)試控制臺(tái)中都會(huì)產(chǎn)生一個(gè) JavaScript 錯(cuò)誤。因此,在對(duì)狀態(tài)代碼進(jìn)行充分的考慮之后,您就可以完全忽略重定向代碼的問(wèn)題了。 結(jié)果是您的請(qǐng)求無(wú)法重定向到其他服務(wù)器上,而不會(huì)產(chǎn)生安全性錯(cuò)誤。在這些情況中,您根本就不會(huì)得到狀態(tài)代碼。通常在調(diào)試控制臺(tái)中都會(huì)產(chǎn)生一個(gè) JavaScript 錯(cuò)誤。因此,在對(duì)狀態(tài)代碼進(jìn)行充分的考慮之后,您就可以完全忽略重定向代碼的問(wèn)題了。 錯(cuò)誤 一旦接收到狀態(tài)代碼 200 并且意識(shí)到可以很大程度上忽略 300 系列的狀態(tài)代碼之后,所需要擔(dān)心的唯一一組代碼就是 400 系列的代碼了,這說(shuō)明了不同類型的錯(cuò)誤。回頭再來(lái)看一下 清單 7,并注意在對(duì)錯(cuò)誤進(jìn)行處理時(shí),只將少數(shù)常見(jiàn)的錯(cuò)誤消息輸出給用戶了。盡管這是朝正確方向前進(jìn)的一步,但是要告訴從事應(yīng)用程序開(kāi)發(fā)的用戶和程序員究竟發(fā)生了什么問(wèn)題,這些消息仍然是沒(méi)有太大用處的。 首先,我們要添加對(duì)找不到的頁(yè)的支持。實(shí)際上這在大部分產(chǎn)品系統(tǒng)中都不應(yīng)該出現(xiàn),但是在測(cè)試腳本位置發(fā)生變化或程序員輸入了錯(cuò)誤的 URL 時(shí),這種情況并不罕見(jiàn)。如果您可以自然地報(bào)告 404 錯(cuò)誤,就可以為那些困擾不堪的用戶和程序員提供更多幫助。例如,如果服務(wù)器上的一個(gè)腳本被刪除了,我們就可以使用 清單 7 中的代碼,這樣用戶就會(huì)看到一個(gè)如 圖 5 所示的非描述性錯(cuò)誤。 邊界情況和困難情況 看到現(xiàn)在,一些新手程序員就可能會(huì)這究竟是要討論什么內(nèi)容。有一點(diǎn)事實(shí)大家需要知道:只有不到 5% 的 Ajax 請(qǐng)求需要使用諸如 2、3 之類的就緒狀態(tài)和諸如 403 之類的狀態(tài)代碼(實(shí)際上,這個(gè)比率可能更接近于 1% 甚至更少)。這些情況非常重要,稱為 邊界情況(edge case) —— 它們只會(huì)在一些非常特殊的情況下發(fā)生,其中遇到的都是最奇特的問(wèn)題。雖然這些情況并不普遍,但是這些邊界情況卻占據(jù)了大部分用戶所碰到的問(wèn)題的 80%! 對(duì)于典型的用戶來(lái)說(shuō),應(yīng)用程序 100 次都是正常工作的這個(gè)事實(shí)通常都會(huì)被忘記,然而應(yīng)用程序只要一次出錯(cuò)就會(huì)被他們清楚地記住。如果您可以很好地處理邊界情況(或困難情況),就可以為再次訪問(wèn)站點(diǎn)的用戶提供滿意的回報(bào)。 圖 5. 常見(jiàn)錯(cuò)誤處理 ![]() 用戶無(wú)法判斷問(wèn)題究竟是認(rèn)證問(wèn)題、沒(méi)找到腳本(此處就是這種情況)、用戶錯(cuò)誤還是代碼中有些地方產(chǎn)生了問(wèn)題。添加一些簡(jiǎn)單的代碼可以讓這個(gè)錯(cuò)誤更加具體。請(qǐng)參照 清單 8,它負(fù)責(zé)處理沒(méi)找到的腳本或認(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)簡(jiǎn)單,但是它的確多提供了一些有用的信息。圖 6 給出了與 圖 5 相同的錯(cuò)誤,但是這一次錯(cuò)誤處理代碼向用戶或程序員更好地說(shuō)明了究竟發(fā)生了什么。 圖 6. 特殊錯(cuò)誤處理 ![]() 在我們自己的應(yīng)用程序中,可以考慮在發(fā)生認(rèn)證失敗的情況時(shí)清除用戶名和密碼,并向屏幕上添加一條錯(cuò)誤消息。我們可以使用類似的方法來(lái)更好地處理找不到腳本或其他 400 類型的錯(cuò)誤(例如 405 表示不允許使用諸如發(fā)送 HEAD 請(qǐng)求之類不可接受的請(qǐng)求方法,而 407 則表示需要進(jìn)行代理認(rèn)證)。然而不管采用哪種選擇,都需要從對(duì)服務(wù)器上返回的狀態(tài)代碼開(kāi)始入手進(jìn)行處理。 其他請(qǐng)求類型 如果您真希望控制 XMLHttpRequest 對(duì)象,可以考慮最后實(shí)現(xiàn)這種功能 —— 將 HEAD 請(qǐng)求添加到指令中。在前兩篇文章中,我們已經(jīng)介紹了如何生成 GET 請(qǐng)求;在馬上就要發(fā)表的一篇文章中,您會(huì)學(xué)習(xí)有關(guān)使用 POST 請(qǐng)求將數(shù)據(jù)發(fā)送到服務(wù)器上的知識(shí)。不過(guò)本著增強(qiáng)錯(cuò)誤處理和信息搜集的精神,您應(yīng)該學(xué)習(xí)如何生成 HEAD 請(qǐng)求。 生成請(qǐng)求 實(shí)際上生成 HEAD 請(qǐng)求非常簡(jiǎn)單;您可以使用 "HEAD"(而不是 "GET" 或 "POST")作為第一個(gè)參數(shù)來(lái)調(diào)用 open() 方法,如 清單 9 所示。 清單 9. 使用 Ajax 生成一個(gè) HEAD 請(qǐng)求 function getSalesData() { createRequest(); var url = "/boards/servlet/UpdateBoardSales"; request.open("HEAD", url, true); request.onreadystatechange = updatePage; request.send(null); } 當(dāng)您這樣生成一個(gè) HEAD 請(qǐng)求時(shí),服務(wù)器并不會(huì)像對(duì) GET 或 POST 請(qǐng)求一樣返回一個(gè)真正的響應(yīng)。相反,服務(wù)器只會(huì)返回資源的 頭(header),這包括響應(yīng)中內(nèi)容最后修改的時(shí)間、請(qǐng)求資源是否存在和很多其他有用信息。您可以在服務(wù)器處理并返回資源之前使用這些信息來(lái)了解有關(guān)資源的信息。 對(duì)于這種請(qǐng)求您可以做的最簡(jiǎn)單的事情就是簡(jiǎn)單地輸出所有的響應(yīng)頭的內(nèi)容。這可以讓您了解通過(guò) HEAD 請(qǐng)求可以使用什么。清單 10 提供了一個(gè)簡(jiǎn)單的回調(diào)函數(shù),用來(lái)輸出從 HEAD 請(qǐng)求中獲得的響應(yīng)頭的內(nèi)容。 清單 10. 輸出從 HEAD 請(qǐng)求中獲得的響應(yīng)頭的內(nèi)容 function updatePage() { if (request.readyState == 4) { alert(request.getAllResponseHeaders()); } } 請(qǐng)參見(jiàn) 圖 7,其中顯示了從一個(gè)向服務(wù)器發(fā)出的 HEAD 請(qǐng)求的簡(jiǎn)單 Ajax 應(yīng)用程序返回的響應(yīng)頭。 您可以單獨(dú)使用這些頭(從服務(wù)器類型到內(nèi)容類型)在 Ajax 應(yīng)用程序中提供其他信息或功能。 檢查 URL 您已經(jīng)看到了當(dāng) URL 不存在時(shí)應(yīng)該如何檢查 404 錯(cuò)誤。如果這變成一個(gè)常見(jiàn)的問(wèn)題 —— 可能是缺少了一個(gè)特定的腳本或 servlet —— 那么您就可能會(huì)希望在生成完整的 GET 或 POST 請(qǐng)求之前來(lái)檢查這個(gè) URL。要實(shí)現(xiàn)這種功能,生成一個(gè) HEAD 請(qǐng)求,然后在回調(diào)函數(shù)中檢查 404 錯(cuò)誤;清單 11 給出了一個(gè)簡(jiǎn)單的回調(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); } } } 誠(chéng)實(shí)地說(shuō),這段代碼的價(jià)值并不太大。服務(wù)器必須對(duì)請(qǐng)求進(jìn)行響應(yīng),并構(gòu)造一個(gè)響應(yīng)來(lái)填充內(nèi)容長(zhǎng)度的響應(yīng)頭,因此并不能節(jié)省任何處理時(shí)間。另外,這花費(fèi)的時(shí)間與生成請(qǐng)求并使用 HEAD 請(qǐng)求來(lái)查看 URL 是否存在所需要的時(shí)間一樣多,因?yàn)樗墒褂?GET 或 POST 的請(qǐng)求,而不僅僅是如 清單 7 所示一樣來(lái)處理錯(cuò)誤代碼。不過(guò),有時(shí)確切地了解目前什么可用也是非常有用的;您永遠(yuǎn)不會(huì)知道何時(shí)創(chuàng)造力就會(huì)迸發(fā)或者何時(shí)需要 HEAD 請(qǐng)求! 有用的 HEAD 請(qǐng)求 您會(huì)發(fā)現(xiàn) HEAD 請(qǐng)求非常有用的一個(gè)領(lǐng)域是用來(lái)查看內(nèi)容的長(zhǎng)度或內(nèi)容的類型。這樣可以確定是否需要發(fā)回大量數(shù)據(jù)來(lái)處理請(qǐng)求,和服務(wù)器是否試圖返回二進(jìn)制數(shù)據(jù),而不是 HTML、文本或 XML(在 JavaScript 中,這 3 種類型的數(shù)據(jù)都比二進(jìn)制數(shù)據(jù)更容易處理)。 在這些情況中,您只使用了適當(dāng)?shù)念^名,并將其傳遞給 XMLHttpRequest 對(duì)象的 getResponseHeader() 方法。因此要獲取響應(yīng)的長(zhǎng)度,只需要調(diào)用 request.getResponseHeader("Content-Length");。要獲取內(nèi)容類型,請(qǐng)使用 request.getResponseHeader("Content-Type");。 在很多應(yīng)用程序中,生成 HEAD 請(qǐng)求并沒(méi)有增加任何功能,甚至可能會(huì)導(dǎo)致請(qǐng)求速度變慢(通過(guò)強(qiáng)制生成一個(gè) HEAD 請(qǐng)求來(lái)獲取有關(guān)響應(yīng)的數(shù)據(jù),然后在使用一個(gè) GET 或 POST 請(qǐng)求來(lái)真正獲取響應(yīng))。然而,在出現(xiàn)您不確定有關(guān)腳本或服務(wù)器端組件的情況時(shí),使用 HEAD 請(qǐng)求可以獲取一些基本的數(shù)據(jù),而不需要對(duì)響應(yīng)數(shù)據(jù)真正進(jìn)行處理,也不需要大量的帶寬來(lái)發(fā)送響應(yīng)。 結(jié)束語(yǔ) 對(duì)于很多 Ajax 和 Web 程序員來(lái)說(shuō),本文中介紹的內(nèi)容似乎是太高級(jí)了。生成 HEAD 請(qǐng)求的價(jià)值是什么呢?到底在什么情況下需要在 JavaScript 中顯式地處理重定向狀態(tài)代碼呢?這些都是很好的問(wèn)題;對(duì)于簡(jiǎn)單的應(yīng)用程序來(lái)說(shuō),答案是這些高級(jí)技術(shù)的價(jià)值并不是非常大。 然而,Web 已經(jīng)不再是只需實(shí)現(xiàn)簡(jiǎn)單應(yīng)用程序的地方了;用戶已經(jīng)變得更加高級(jí),客戶期望能夠獲得更好的穩(wěn)定性、更高級(jí)的錯(cuò)誤報(bào)告,如果應(yīng)用程序有 1% 的時(shí)間停機(jī),那么經(jīng)理就可能會(huì)因此而被解雇。 因此您的工作就不能僅僅局限于簡(jiǎn)單的應(yīng)用程序了,而是需要更深入理解 XMLHttpRequest。 ·如果您可以考慮各種就緒狀態(tài) —— 并且理解了這些就緒狀態(tài)在不同瀏覽器之間的區(qū)別 —— 就可以快速調(diào)試應(yīng)用程序了。您甚至可以基于就緒狀態(tài)而開(kāi)發(fā)一些創(chuàng)造性的功能,并向用戶和客戶回報(bào)請(qǐng)求的狀態(tài)。 ·如果您要對(duì)狀態(tài)代碼進(jìn)行控制,就可以設(shè)置應(yīng)用程序來(lái)處理腳本錯(cuò)誤、非預(yù)期的響應(yīng)以及邊緣情況。結(jié)果是應(yīng)用程序在所有的時(shí)間都可以正常工作,而不僅僅是只能一切都正常的情況下才能運(yùn)行。 ·增加這種生成 HEAD 請(qǐng)求的能力,檢查某個(gè) URL 是否存在,以及確認(rèn)某個(gè)文件是否被修改過(guò),這樣就可以確保用戶可以獲得有效的頁(yè)面,用戶所看到的信息都是最新的,(最重要的是)讓他們驚訝這個(gè)應(yīng)用程序是如何健壯和通用。 本文的目的并非是要讓您的應(yīng)用程序顯得十分華麗,而是幫助您去掉黃色聚光燈后重點(diǎn)昭顯文字的美麗,或者外觀更像桌面一樣。盡管這些都是 Ajax 的功能(在后續(xù)幾篇文章中就會(huì)介紹),不過(guò)它們卻像是蛋糕表面的一層奶油。如果您可以使用 Ajax 來(lái)構(gòu)建一個(gè)堅(jiān)實(shí)的基礎(chǔ),讓應(yīng)用程序可以很好地處理錯(cuò)誤和問(wèn)題,用戶就會(huì)返回您的站點(diǎn)和應(yīng)用程序。在接下來(lái)的文章中,我們將添加這種直觀的技巧,這會(huì)讓客戶興奮得發(fā)抖。 第 4 頁(yè) 利用 DOM 進(jìn)行 Web 響應(yīng)
程序員(使用后端應(yīng)用程序)和 Web 程序員(編寫 HTML、CSS 和 JavaScript)之間的分水嶺是長(zhǎng)久存在的。但是,Document Object Model (DOM) 彌補(bǔ)了這個(gè)裂縫,使得在后端使用 XML 同時(shí)在前端使用 HTML 切實(shí)可行,并成為極其有效的工具。在本文中,Brett McLaughlin 介紹了 Document Object Model,解釋它在 Web 頁(yè)面中的應(yīng)用,并開(kāi)始挖掘其在 JavaScript 中的用途。 與許多 Web 程序員一樣,您可能使用過(guò) HTML。HTML 是程序員開(kāi)始與 Web 頁(yè)面打交道的方式;HTML 通常是他們完成應(yīng)用程序或站點(diǎn)前的最后一步——調(diào)整一些布局、顏色或樣式。不過(guò),雖然經(jīng)常使用 HTML,但對(duì)于 HTML 轉(zhuǎn)到瀏覽器呈現(xiàn)在屏幕上時(shí)到底發(fā)生了什么,人們普遍存在誤解。在我分析您認(rèn)為可能發(fā)生的事情及其可能錯(cuò)誤的原因之前,我希望您對(duì)設(shè)計(jì)和服務(wù) Web 頁(yè)面時(shí)涉及的過(guò)程一清二楚: 1、一些人(通常是您!)在文本編輯器或 IDE 中創(chuàng)建 HTML。 2、然后您將 HTML 上載到 Web 服務(wù)器,例如 Apache HTTPD,并將其公開(kāi)在 Internet 或 intranet 上。 3、用戶用 Firefox 或 SafariA 等瀏覽器請(qǐng)求您的 Web 頁(yè)面。 4、用戶的瀏覽器向您的服務(wù)器請(qǐng)求 HTML。 5、瀏覽器將從服務(wù)器接收到的頁(yè)面以圖形和文本方式呈現(xiàn);用戶看到并激活 Web 頁(yè)面。 這看起來(lái)非常基礎(chǔ),但事情很快會(huì)變得有趣起來(lái)。事實(shí)上,步驟 4 和步驟 5 之間發(fā)生的巨大數(shù)量的 “填充物(stuff)” 就是本文的焦點(diǎn)。術(shù)語(yǔ) “填充物” 也十分適用,因?yàn)槎鄶?shù)程序員從來(lái)沒(méi)有真正考慮過(guò)當(dāng)用戶瀏覽器請(qǐng)求顯示標(biāo)記時(shí)到底在標(biāo)記身上發(fā)生了什么。 ·是否瀏覽器只是讀取 HTML 中的文本并將其顯示? ·CSS 呢?尤其是當(dāng) CSS 位于外部文件時(shí)。 ·JavaScript 呢?它也通常位于外部文件中。 ·瀏覽器如何處理這些項(xiàng),如果將事件處理程序、函數(shù)和樣式映射到該文本標(biāo)記? 實(shí)踐證明,所有這些問(wèn)題的答案都是 Document Object Model。因此,廢話少說(shuō),直接研究 DOM。 Web 程序員和標(biāo)記 對(duì)于多數(shù)程序員,當(dāng) Web 瀏覽器開(kāi)始時(shí)他們的工作就結(jié)束了。也就是說(shuō),將一個(gè) HTML 文件放入 Web 瀏覽器的目錄上后,您通常就認(rèn)為它已經(jīng)“完成”,而且(滿懷希望地)認(rèn)為再也不會(huì)考慮它!說(shuō)到編寫干凈、組織良好的頁(yè)面時(shí),這也是一個(gè)偉大的目標(biāo);希望您的標(biāo)記跨瀏覽器、用各種版本的 CSS 和 JavaScript 顯示它應(yīng)該顯示的內(nèi)容,一點(diǎn)錯(cuò)都沒(méi)有。 問(wèn)題是這種方法限制了程序員對(duì)瀏覽器中真正發(fā)生的事情的理解。更重要的是,它限制了您用客戶端 JavaScript 動(dòng)態(tài)更新、更改和重構(gòu) Web 頁(yè)面的能力。擺脫這種限制,讓您的 Web 站點(diǎn)擁有更大的交互性和創(chuàng)造性。 程序員做什么 作為典型的 Web 程序員,您可能啟動(dòng)文本編輯和 IDE 后就開(kāi)始輸入 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 頁(yè)面的真正潛力或其他任何元物質(zhì)的講座。您需要了解的是您在 Web 開(kāi)發(fā)中到底是什么角色。 說(shuō)到頁(yè)面的外觀,頂多您只能提提建議。您提供 CSS 樣式表時(shí),用戶可以覆蓋您的樣式選擇。您提供字體大小時(shí),用戶瀏覽器可以為視障者更改這些大小,或者在大顯示器(具有同等大的分辨率)上按比例縮小。甚至您選擇的顏色和字體也受制于用戶顯示器和用戶在其系統(tǒng)上安裝的字體。雖然盡您所能來(lái)設(shè)計(jì)頁(yè)面樣式很不錯(cuò),但這絕不是 您對(duì) Web 頁(yè)面的最大影響。 您絕對(duì)控制的是 Web 頁(yè)面的結(jié)構(gòu)。您的標(biāo)記不可更改,用戶就不能亂弄;他們的瀏覽器只能從您的 Web 服務(wù)器檢索標(biāo)記并顯示它(雖然樣式更符合用戶的品味而不是您自己的品味)。但頁(yè)面組織,不管是在該段落內(nèi)還是在其他分區(qū),都只由您單獨(dú)決定。要是想實(shí)際更改您的頁(yè)面(這是大多數(shù) Ajax 應(yīng)用程序所關(guān)注的),您操作的是頁(yè)面的結(jié)構(gòu)。盡管很容易更改一段文本的顏色,但在現(xiàn)有頁(yè)面上添加文本或整個(gè)區(qū)段要難得多。不管用戶如何設(shè)計(jì)該區(qū)段的樣式,都是由您控制頁(yè)面本身的組織。 標(biāo)記做什么 一旦意識(shí)到您的標(biāo)記是真正與組織相關(guān)的,您就會(huì)對(duì)它另眼相看了。不會(huì)認(rèn)為 h1 導(dǎo)致文本是大字號(hào)、黑色、粗體的,而會(huì)認(rèn)為 h1 是標(biāo)題。用戶如何看待這個(gè)問(wèn)題以及他們是使用您的 CSS、他們自己的 CSS 還是這兩者的組合,這是次要的考慮事項(xiàng)。相反,要意識(shí)到只有標(biāo)記才能提供這種級(jí)別的組織;p 指明文本在段落內(nèi),img 表示圖像,div 將頁(yè)面分成區(qū)段,等等。 還應(yīng)該清楚,樣式和行為(事件處理程序和 JavaScript)是在事后 應(yīng)用于該組織的。標(biāo)記就緒以后才能對(duì)其進(jìn)行操作或設(shè)計(jì)樣式。所以,正如您可以將 CSS 保存在 HTML 的外部文件中一樣,標(biāo)記的組織與其樣式、格式和行為是分離的。雖然您肯定可以用 JavaScript 更改元素或文本的樣式,但實(shí)際更改您的標(biāo)記所布置的組織卻更加有趣。 只要牢記您的標(biāo)記只為您的頁(yè)面提供組織、框架,您就能立于不敗之地。再前進(jìn)一小步,您就會(huì)明白瀏覽器是如何接受所有的文本組織并將其轉(zhuǎn)變?yōu)槌?jí)有趣的一些東西的,即一組對(duì)象,其中每個(gè)對(duì)象都可被更改、添加或刪除。 文本標(biāo)記的優(yōu)點(diǎn) 在討論 Web 瀏覽器之前,值得考慮一下為什么純文本絕對(duì) 是存儲(chǔ) HTML 的最佳選擇(有關(guān)詳細(xì)信息,請(qǐng)參閱 有關(guān)標(biāo)記的一些其他想法)。不考慮優(yōu)缺點(diǎn),只是回憶一下在每次查看頁(yè)面時(shí) HTML 是通過(guò)網(wǎng)絡(luò)發(fā)送到 Web 瀏覽器的(為了簡(jiǎn)潔,不考慮高速緩存等)。真是再?zèng)]有比傳遞文本再有效的方法了。二進(jìn)制對(duì)象、頁(yè)面圖形表示、重新組織的標(biāo)記塊等等,所有這一切都比純文本文件通過(guò)網(wǎng)絡(luò)傳遞要更困難。 此外,瀏覽器也為此增光添彩。今天的瀏覽器允許用戶更改文本大小、按比例伸縮圖像、下載頁(yè)面的 CSS 或 JavaScript(大多數(shù)情況),甚至更多,這完全排除了將任何類型的頁(yè)面圖形表示發(fā)送到瀏覽器上。但是,瀏覽器需要原 HTML,這樣它才能在瀏覽器中對(duì)頁(yè)面應(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)容(頁(yè)面中的數(shù)據(jù))與表示和樣式(通常由 CSS 應(yīng)用)分離。如果程序員要將 HTML 與 CSS 分離,然后強(qiáng)制瀏覽器檢索粘結(jié)頁(yè)面各部分的一些頁(yè)面表示,這會(huì)失去這些標(biāo)準(zhǔn)的多數(shù)優(yōu)點(diǎn)。保持這些部分到達(dá)瀏覽器時(shí)都一直分離使得瀏覽器在從服務(wù)器獲取 HTML 時(shí)有了前所未有的靈活性。 關(guān)于標(biāo)記的其他想法 純文本編輯:是對(duì)是錯(cuò)? 純文本是存儲(chǔ)標(biāo)記的理想選擇,但是不適合編輯 標(biāo)記。大行其道的是使用 IDE,比如 Macromedia DreamWeaver 或更強(qiáng)勢(shì)點(diǎn)的 Microsoft? FrontPage?,來(lái)操作 Web 頁(yè)面標(biāo)記。這些環(huán)境通常提供快捷方式和幫助來(lái)創(chuàng)建 Web 頁(yè)面,尤其是在使用 CSS 和 JavaScript 時(shí),二者都來(lái)自實(shí)際頁(yè)面標(biāo)記以外的文件。許多人仍偏愛(ài)好用古老的記事本或 vi(我承認(rèn)我也是其中一員),這并不要緊。不管怎樣,最終結(jié)果都是充滿標(biāo)記的文本文件。 網(wǎng)絡(luò)上的文本:好東西 已經(jīng)說(shuō)過(guò),文本是文檔的最好媒體,比如 HTML 或 CSS,在網(wǎng)絡(luò)上被千百次地傳輸。當(dāng)我說(shuō)瀏覽器表示文本很難時(shí),是特指將文本轉(zhuǎn)換為用戶查看的可視圖形頁(yè)面。這與瀏覽器實(shí)際上如何從 Web 瀏覽器檢索頁(yè)面沒(méi)有關(guān)系;在這種情況下,文本仍然是最佳選擇。
文本標(biāo)記的缺點(diǎn) 正如文本標(biāo)記對(duì)于設(shè)計(jì)人員和頁(yè)面創(chuàng)建者具有驚人的優(yōu)點(diǎn)之外,它對(duì)于瀏覽器也具有相當(dāng)出奇的缺點(diǎn)。具體來(lái)說(shuō),瀏覽器很難直接將文本標(biāo)記可視地表示給用戶(詳細(xì)信息請(qǐng)參閱 有關(guān)標(biāo)記的一些其他想法)。考慮下列常見(jiàn)的瀏覽器任務(wù): ·基于元素類型、類、ID 及其在 HTML 文檔中的位置,將 CSS 樣式(通常來(lái)自外部文件中的多個(gè)樣式表)應(yīng)用于標(biāo)記。 ·基于 JavaScript 代碼(通常位于外部文件)將樣式和格式應(yīng)用于 HTML 文檔的不同部分。 ·基于 JavaScript 代碼更改表單字段的值。 ·基于 JavaScript 代碼,支持可視效果,比如圖像翻轉(zhuǎn)和圖像交換。 復(fù)雜性并不在于編碼這些任務(wù);其中每件事都是相當(dāng)容易的。復(fù)雜性來(lái)自實(shí)際實(shí)現(xiàn)請(qǐng)求動(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)用無(wú)樣式的 HTML,然后事后應(yīng)用格式? 這些非常困難的問(wèn)題是如今很少有人編寫瀏覽器的原因。(編寫瀏覽器的人應(yīng)該接受最由衷的感謝) 無(wú)疑,純文本不是存儲(chǔ)瀏覽器 HTML 的好辦法,盡管文本是獲取頁(yè)面標(biāo)記最好的解決方案。如果加上 JavaScript 更改 頁(yè)面結(jié)構(gòu)的能力,事情就變得有些微妙了。瀏覽器應(yīng)該將修改過(guò)的結(jié)構(gòu)重新寫入磁盤嗎?如何才能保持文檔的最新版本呢? 無(wú)疑,文本不是答案。它難以修改,為其應(yīng)用樣式和行為很困難,與今天 Web 頁(yè)面的動(dòng)態(tài)本質(zhì)在根本上相去甚遠(yuǎn)。 求助于樹(shù)視圖 這個(gè)問(wèn)題的答案(至少是由當(dāng)今 Web 瀏覽器選擇的答案)是使用樹(shù)結(jié)構(gòu)來(lái)表示 HTML。參見(jiàn) 清單 1,這是一個(gè)表示為本文標(biāo)記的相當(dāng)簡(jiǎn)單又無(wú)聊的 HTML 頁(yè)面。 清單 1. 文本標(biāo)記中的簡(jiǎn)單 HTML 頁(yè)面 <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> 瀏覽器接受該頁(yè)面并將之轉(zhuǎn)換為樹(shù)形結(jié)構(gòu),如圖 1 所示。 ![]() 為了保持本文的進(jìn)度,我做了少許簡(jiǎn)化。DOM 或 XML 方面的專家會(huì)意識(shí)到空白對(duì)于文檔文本在 Web 瀏覽器樹(shù)結(jié)構(gòu)中表示和分解方式的影響。膚淺的了解只會(huì)使事情變得模棱兩可,所以如果想弄清空白的影響,那最好不過(guò)了;如果不想的話,那可以繼續(xù)讀下去,不要考慮它。當(dāng)它成為問(wèn)題時(shí),那時(shí)您就會(huì)明白您需要的一切。 除了實(shí)際的樹(shù)背景之外,可能會(huì)首先注意到樹(shù)中的一切是以最外層的 HTML 包含元素,即 html 元素開(kāi)始的。使用樹(shù)的比喻,這叫做根元素。所以即使這是樹(shù)的底層,當(dāng)您查看并分析樹(shù)的時(shí)候,我也通常以此開(kāi)始。如果它確實(shí)奏效,您可以將整個(gè)樹(shù)顛倒一下,但這確實(shí)有些拓展了樹(shù)的比喻。 從根流出的線表示不同標(biāo)記部分之間的關(guān)系。head 和 body 元素是 html 根元素的孩子;title 是 head 的孩子,而文本 “Trees, trees, everywhere” 是 title 的孩子。整個(gè)樹(shù)就這樣組織下去,直到瀏覽器獲得與 圖 1 類似的結(jié)構(gòu)。 一些附加術(shù)語(yǔ) 為了沿用樹(shù)的比喻,head 和 body 被叫做 html 的分支(branches)。叫分支是因?yàn)樗鼈冇凶约旱暮⒆印.?dāng)?shù)竭_(dá)樹(shù)的末端時(shí),您將進(jìn)入主要的文本,比如 “Trees, trees, everywhere” 和 “really”;這些通常稱為葉子,因?yàn)樗鼈儧](méi)有自己的孩子。您不需要記住所有這些術(shù)語(yǔ),當(dāng)您試圖弄清楚特定術(shù)語(yǔ)的意思時(shí),只要想像一下樹(shù)結(jié)構(gòu)就容易多了。 對(duì)象的值 既然了解了一些基本的術(shù)語(yǔ),現(xiàn)在應(yīng)該關(guān)注一下其中包含元素名稱和文本的小矩形了(圖 1)。每個(gè)矩形是一個(gè)對(duì)象;瀏覽器在其中解決一些文本問(wèn)題。通過(guò)使用對(duì)象來(lái)表示 HTML 文檔的每一部分,可以很容易地更改組織、應(yīng)用樣式、允許 JavaScript 訪問(wèn)文檔,等等。 對(duì)象類型和屬性 標(biāo)記的每個(gè)可能類型都有自己的對(duì)象類型。例如,HTML 中的元素用 Element 對(duì)象類型表示。文檔中的文本用 Text 類型表示,屬性用 Attribute 類型表示,以此類推。 所以 Web 瀏覽器不僅可以使用對(duì)象模型來(lái)表示文檔(從而避免了處理靜態(tài)文本),還可以用對(duì)象類型立即辨別出某事物是什么。HTML 文檔被解析并轉(zhuǎn)換為對(duì)象集合,如 圖 1 所示,然后尖括號(hào)和轉(zhuǎn)義序列(例如,使用 < 表示 <,使用 > 表示 >)等事物不再是問(wèn)題了。這就使得瀏覽器的工作(至少在解析輸入 HTML 之后)變得更容易。弄清某事物究竟是元素還是屬性并確定如何處理該類型的對(duì)象,這些操作都十分簡(jiǎn)單了。 通過(guò)使用對(duì)象,Web 瀏覽器可以更改這些對(duì)象的屬性。例如,每個(gè)元素對(duì)象具有一個(gè)父元素和一系列子元素。所以添加新的子元素或文本只需要向元素的子元素列表中添加一個(gè)新的子元素。這些對(duì)象還具有 style 屬性,所以快速更改元素或文本段的樣式非常簡(jiǎn)單。例如,要使用 JavaScript 更改 div 的高度,如下所示: someDiv.style.height = "300px"; 換句話說(shuō),Web 瀏覽器使用對(duì)象屬性可以非常容易地更改樹(shù)的外觀和結(jié)構(gòu)。將之比作瀏覽器在內(nèi)部將頁(yè)面表示為文本時(shí)必須進(jìn)行的復(fù)雜事情,每次更改屬性或結(jié)構(gòu)都需要瀏覽器重新編寫靜態(tài)文件、重新解析并在屏幕上重新顯示。有了對(duì)象,所有這一切都解決了。 現(xiàn)在,花點(diǎn)時(shí)間展開(kāi)一些 HTML 文檔并用樹(shù)將其勾畫(huà)出來(lái)。盡管這看起來(lái)是個(gè)不尋常的請(qǐng)求(尤其是在包含極少代碼的這樣一篇文章中),如果您希望能夠操縱這些樹(shù),那么需要熟悉它們的結(jié)構(gòu)。 在這個(gè)過(guò)程中,可能會(huì)發(fā)現(xiàn)一些古怪的事情。比如,考慮下列情況: ·屬性發(fā)生了什么? ·分解為元素(比如 em 和 b)的文本呢? ·結(jié)構(gòu)不正確(比如當(dāng)缺少結(jié)束 p 標(biāo)記時(shí))的 HTML 呢? 一旦熟悉這些問(wèn)題之后,就能更好地理解下面幾節(jié)了。 嚴(yán)格有時(shí)是好事 如果嘗試剛提到的練習(xí) I,您可能會(huì)發(fā)現(xiàn)標(biāo)記的樹(shù)視圖中存在一些潛在問(wèn)題(如果不練習(xí)的話,那就聽(tīng)我說(shuō)吧!)。事實(shí)上,在 清單 1 和 圖 1 中就會(huì)發(fā)現(xiàn)一些問(wèn)題,首先看 p 元素是如何分解的。如果您問(wèn)通常的 Web 開(kāi)發(fā)人員 “p 元素的文本內(nèi)容是什么”,最常見(jiàn)的答案將是 “Welcome to a really boring Web page.”。如果將之與圖 1 做比較,將會(huì)發(fā)現(xiàn)這個(gè)答案(雖然合乎邏輯)是根本不正確的。 實(shí)際上,p 元素具有三個(gè) 不同的子對(duì)象,其中沒(méi)有一個(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)換為某種類型的對(duì)象。 此外,順序無(wú)關(guān)緊要!如果瀏覽器顯示正確的對(duì)象,但顯示順序與您在 HTML 中提供的順序不同,那么您能想像出用戶將如何響應(yīng) Web 瀏覽器嗎?段落夾在頁(yè)面標(biāo)題和文章標(biāo)題中間,而這不是您自己組織文檔時(shí)的樣式呢?很顯然,瀏覽器必須保持元素和文本的順序。 在本例中,p 元素有三個(gè)不同部分: ·em 元素之前的文本 ·em 元素本身 ·em 元素之后的文本 如果將該順序打亂,可能會(huì)把重點(diǎn)放在文本的錯(cuò)誤部分。為了保持一切正常,p 元素有三個(gè)子對(duì)象,其順序是在 清單 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ù)時(shí)應(yīng)用的規(guī)則有多么嚴(yán)格,并真正幫助您掌握 HTML 及其樹(shù)結(jié)構(gòu)。 屬性呢? 當(dāng)您試圖弄清楚如何處理屬性時(shí),是否遇到一些問(wèn)題呢?前已提及,屬性確實(shí)具有自己的對(duì)象類型,但屬性確實(shí)不是顯示它的元素的子元素,嵌套元素和文本不在同一屬性 “級(jí)別”,您將注意到,清單 2 和 3 中練習(xí)的答案沒(méi)有顯示屬性。 屬性事實(shí)上存儲(chǔ)在瀏覽器使用的對(duì)象模型中,但它們有一些特殊情況。每個(gè)元素都有可用屬性的列表,且與子對(duì)象列表是分離的。所以 div 元素可能有一個(gè)包含屬性 “id” 和另一個(gè)屬性 “class” 的列表。 記住,元素的屬性必須具有惟一的名稱,也就是說(shuō),一個(gè)元素不能有兩個(gè) “id” 或兩個(gè) “class” 屬性。這使得列表易于維護(hù)和訪問(wèn)。在下一篇文章將會(huì)看到,您可以簡(jiǎn)單調(diào)用諸如 getAttribute("id") 的方法來(lái)按名稱獲取屬性的值。還可以用相似的方法調(diào)用來(lái)添加屬性或設(shè)置(重置)現(xiàn)有屬性的值。 值得指出的是,屬性名的惟一性使得該列表不同于子對(duì)象列表。p 元素可以有多個(gè) em 元素,所以子對(duì)象列表可以包含多個(gè)重復(fù)項(xiàng)。盡管子項(xiàng)列表和屬性列表的操作方式相似,但一個(gè)可以包含重復(fù)項(xiàng)(對(duì)象的子項(xiàng)),而一個(gè)不能(元素對(duì)象的屬性)。最后,只有元素具有屬性,所以文本對(duì)象沒(méi)有用于存儲(chǔ)屬性的附加列表。 凌亂的 HTML 在繼續(xù)之前,談到瀏覽器如何將標(biāo)記轉(zhuǎn)換為樹(shù)表示,還有一個(gè)主題值得探討,即瀏覽器如何處理不是格式良好的標(biāo)記。格式良好 是 XML 廣泛使用的一個(gè)術(shù)語(yǔ),有兩個(gè)基本意思: ·每個(gè)開(kāi)始標(biāo)記都有一個(gè)與之匹配的結(jié)束標(biāo)記。所以每個(gè) <p> 在文檔中與 </p> 匹配,每個(gè) <div> 與 </div> 匹配,等等。 ·最里面的開(kāi)始標(biāo)記與最里面的結(jié)束標(biāo)記相匹配,然后次里面的開(kāi)始標(biāo)記與次里面的結(jié)束標(biāo)記相匹配,依此類推。所以 <b><i>bold and italics</b></i> 是不合法的,因?yàn)樽罾锩娴拈_(kāi)始標(biāo)記 <i> 與最里面的結(jié)束標(biāo)記 <b> 匹配不當(dāng)。要使之格式良好,要么 切換開(kāi)始標(biāo)記順序,要么 切換結(jié)束標(biāo)記順序。(如果兩者都切換,則仍會(huì)出現(xiàn)問(wèn)題)。 深入研究這兩條規(guī)則。這兩條規(guī)則不僅簡(jiǎn)化了文檔的組織,還消除了不定性。是否應(yīng)先應(yīng)用粗體后應(yīng)用斜體?或恰恰相反?如果覺(jué)得這種順序和不定性不是大問(wèn)題,那么請(qǐng)記住,CSS 允許規(guī)則覆蓋其他規(guī)則,所以,例如,如果 b 元素中文本的字體不同于 i 元素中的字體,則格式的應(yīng)用順序?qū)⒆兊梅浅V匾R虼耍琀TML 的格式良好性有著舉足輕重的作用。 如果瀏覽器收到了不是格式良好的文檔,它只會(huì)盡力而為。得到的樹(shù)結(jié)構(gòu)在最好情況下將是作者希望的原始頁(yè)面的近似,最壞情況下將面目全非。如果您曾將頁(yè)面加載到瀏覽器中后看到完全出乎意料的結(jié)果,您可能在看到瀏覽器結(jié)果時(shí)會(huì)猜想您的結(jié)構(gòu)應(yīng)該如何,并沮喪地繼續(xù)工作。當(dāng)然,搞定這個(gè)問(wèn)題相當(dāng)簡(jiǎn)單:確保文檔是格式良好的!如果不清楚如何編寫標(biāo)準(zhǔn)化的 HTML,請(qǐng)咨詢 參考資料 獲得幫助。 DOM 簡(jiǎn)介 到目前為止,您已經(jīng)知道瀏覽器將 Web 頁(yè)面轉(zhuǎn)換為對(duì)象表示,可能您甚至?xí)孪耄瑢?duì)象表示是 DOM 樹(shù)。DOM 表示 Document Object Model,是一個(gè)規(guī)范,可從 World Wide Web Consortium (W3C) 獲得(您可以參閱 參考資料 中的一些 DOM 相關(guān)鏈接)。 但更重要的是,DOM 定義了對(duì)象的類型和屬性,從而允許瀏覽器表示標(biāo)記。(本系列下一篇文章將專門講述在 JavaScript 和 Ajax 代碼中使用 DOM 的規(guī)范。) 文檔對(duì)象 首先,需要訪問(wèn)對(duì)象模型本身。這非常容易;要在運(yùn)行于 Web 頁(yè)面上的任何 JavaScript 代碼中使用內(nèi)置 document 變量,可以編寫如下代碼: var domTree = document; 當(dāng)然,該代碼本身沒(méi)什么用,但它演示了每個(gè) Web 瀏覽器使得 document 對(duì)象可用于 JavaScript 代碼,并演示了對(duì)象表示標(biāo)記的完整樹(shù)(圖 1)。 每項(xiàng)都是一個(gè)節(jié)點(diǎn) 顯然,document 對(duì)象很重要,但這只是開(kāi)始。在進(jìn)一步深入之前,需要學(xué)習(xí)另一個(gè)術(shù)語(yǔ):節(jié)點(diǎn)。您已經(jīng)知道標(biāo)記的每個(gè)部分都由一個(gè)對(duì)象表示,但它不只是一個(gè)任意的對(duì)象,它是特定類型的對(duì)象,一個(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è)表單字段)。所以即使您沒(méi)有意識(shí)到這一點(diǎn),但您每次將 document 鍵入 JavaScript 代碼時(shí)都會(huì)使用 DOM。 詳細(xì)解釋已經(jīng)學(xué)過(guò)的術(shù)語(yǔ),DOM 樹(shù)是對(duì)象的樹(shù),但更具體地說(shuō),它是節(jié)點(diǎn) 對(duì)象的樹(shù)。在 Ajax 應(yīng)用程序中或任何其他 JavaScript 中,可以使用這些節(jié)點(diǎn)產(chǎn)生下列效果,比如移除元素及其內(nèi)容,突出顯示特定文本,或添加新圖像元素。因?yàn)槎及l(fā)生在客戶端(運(yùn)行在 Web 瀏覽器中的代碼),所以這些效果立即發(fā)生,而不與服務(wù)器通信。最終結(jié)果通常是應(yīng)用程序感覺(jué)起來(lái)響應(yīng)更快,因?yàn)楫?dāng)請(qǐng)求轉(zhuǎn)向服務(wù)器時(shí)以及解釋響應(yīng)時(shí),Web 頁(yè)面上的內(nèi)容更改不會(huì)出現(xiàn)長(zhǎng)時(shí)間的停頓。 在多數(shù)編程語(yǔ)言中,需要學(xué)習(xí)每種節(jié)點(diǎn)類型的實(shí)際對(duì)象名稱,學(xué)習(xí)可用的屬性,并弄清楚類型和強(qiáng)制轉(zhuǎn)換;但在 JavaScript 中這都不是必需的。您可以只創(chuàng)建一個(gè)變量,并為它分配您希望的對(duì)象(正如您已經(jīng)看到的): var domTree = document; var phoneNumberElement = document.getElementById("phone"); var phoneNumber = phoneNumberElement.value; 沒(méi)有類型,JavaScript 根據(jù)需要?jiǎng)?chuàng)建變量并為其分配正確的類型。結(jié)果,從 JavaScript 中使用 DOM 變得微不足道(將來(lái)有一篇文章會(huì)專門講述與 XML 相關(guān)的 DOM,那時(shí)將更加巧妙)。 結(jié)束語(yǔ) 在這里,我要給您留一點(diǎn)懸念。顯然,這并非是對(duì) DOM 完全詳盡的說(shuō)明;事實(shí)上,本文不過(guò)是 DOM 的簡(jiǎn)介。DOM 的內(nèi)容要遠(yuǎn)遠(yuǎn)多于我今天介紹的這些! 本系列的下一篇文章將擴(kuò)展這些觀點(diǎn),并深入探討如何在 JavaScript 中使用 DOM 來(lái)更新 Web 頁(yè)面、快速更改 HTML 并為您的用戶創(chuàng)建更交互的體驗(yàn)。在后面專門講述在 Ajax 請(qǐng)求中使用 XML 的文章中,我將再次返回來(lái)討論 DOM。所以要熟悉 DOM,它是 Ajax 應(yīng)用程序的一個(gè)主要部分。 此時(shí),深入了解 DOM 將十分簡(jiǎn)單,比如詳細(xì)設(shè)計(jì)如何在 DOM 樹(shù)中移動(dòng)、獲得元素和文本的值、遍歷節(jié)點(diǎn)列表,等等,但這可能會(huì)讓您有這種印象,即 DOM 是關(guān)于代碼的,而事實(shí)上并非如此。 在閱讀下一篇文章之前,試著思考一下樹(shù)結(jié)構(gòu)并用一些您自己的 HTML 實(shí)踐一下,以查看 Web 瀏覽器是如何將 HTML 轉(zhuǎn)換為標(biāo)記的樹(shù)視圖的。此外,思考一下 DOM 樹(shù)的組織,并用本文介紹的特殊情況實(shí)踐一下:屬性、有元素混合在其中的文本、沒(méi)有 文本內(nèi)容的元素(比如 img 元素)。 如果扎實(shí)掌握了這些概念,然后學(xué)習(xí)了 JavaScript 和 DOM 的語(yǔ)法(下一篇文章),則會(huì)使得響應(yīng)更為容易。 而且不要忘了,這里有清單 2 和 3 的答案,其中還包含了示例代碼! 圖 2. 清單 2 的答案 ![]() 圖 3. 清單 3 的答案 ![]() |
|