[四天學會ajax] 學習Ajax教程第二天,JavaScript 和 Ajax 發出異步請求(上)

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

本系列的上一期文章(請參閱 參考資料 中的鏈接),我們介紹了 Ajax 應用程序,考察了推動 Ajax 應用程序的基本概念。其中的核心是很多您可能已經瞭解的技術:JavaScript、HTML 和 XHTML、一點動態 HTML 以及 DOM(文檔對象模型)。本文將放大其中的一點,把目光放到具體的 Ajax 細節上。
本文中,您將開始接觸最基本和基礎性的有關 Ajax 的全部對象和編程方法:XMLHttpRequest 對象。該對象實際上僅僅是一個跨越所有 Ajax 應用程序的公共線程,您可能已經預料到,只有徹底理解該對象才能充分發揮編程的潛力。事實上,有時您會發現,要正確地使用 XMLHttpRequest,顯然不能 使用 XMLHttpRequest。這到底是怎麼回事呢?
Web 2.0 一瞥

在深入研究代碼之前首先看看最近的觀點 —— 一定要十分清楚 Web 2.0 這個概念。聽到 Web 2.0 這個詞的時候,應該首先問一問 “Web 1.0 是什麼?” 雖然很少聽人提到 Web 1.0,實際上它指的就是具有完全不同的請求和響應模型的傳統 Web。比如,到 Amazon.com 網站上點擊一個按鈕或者輸入搜索項。就會對服務器發送一個請求,然後響應再返回到瀏覽器。該請求不僅僅是圖書和書目列表,而是另一個完整的 HTML 頁面。因此當 Web 瀏覽器用新的 HTML 頁面重繪時,可能會看到閃爍或抖動。事實上,通過看到的每個新頁面可以清晰地看到請求和響應。
Web 2.0(在很大程度上)消除了這種看得見的往復交互。比如訪問 Google Maps 或 Flickr 這樣的站點(到這些支持 Web 2.0 和 Ajax 站點的鏈接請參閱
參考資料)。比如在 Google Maps 上,您可以拖動地圖,放大和縮小,只有很少的重繪操作。當然這裏仍然有請求和響應,只不過都藏到了幕後。作爲用戶,體驗更加舒適,感覺很像桌面應用程序。這種新的感受和範型就是當有人提到 Web 2.0 時您所體會到的。
需要關心的是如何使這些新的交互成爲可能。顯然,仍然需要發出請求和接收響應,但正是針對每次請求/響應交互的 HTML 重繪造成了緩慢、笨拙的 Web 交互的感受。因此很清楚,我們需要一種方法使發送的請求和接收的響應只 包含需要的數據而不是整個 HTML 頁面。惟一需要獲得整個新 HTML 頁面的時候就是希望用戶看到 新頁面的時候。
但多數交互都是在已有頁面上增加細節、修改主體文本或者覆蓋原有數據。這些情況下,Ajax 和 Web 2.0 方法允許在不 更新整個 HTML 頁面的情況下發送和接收數據。對於那些經常上網的人,這種能力可以讓您的應用程序感覺更快、響應更及時,讓他們不時地光顧您的網站。
XMLHttpRequest 簡介

要真正實現這種絢麗的奇蹟,必須非常熟悉一個 JavaScript 對象,即 XMLHttpRequest。這個小小的對象實際上已經在幾種瀏覽器中存在一段時間了,它是本專欄今後幾個月中要介紹的 Web 2.0、Ajax 和大部分其他內容的核心。爲了讓您快速地大體瞭解它,下面給出將要用於該對象的很少的幾個 方法和屬性。
·open():建立到服務器的新請求。
·send():向服務器發送請求。
·abort():退出當前請求。
·readyState:提供當前 HTML 的就緒狀態。
·responseText:服務器返回的請求響應文本。
如果不瞭解這些(或者其中的任何 一個),您也不用擔心,後面幾篇文章中我們將介紹每個方法和屬性。現在應該 瞭解的是,明確用 XMLHttpRequest 做什麼。要注意這些方法和屬性都與發送請求及處理響應有關。事實上,如果看到 XMLHttpRequest 的所有方法和屬性,就會發現它們都 與非常簡單的請求/響應模型有關。顯然,我們不會遇到特別新的 GUI 對象或者創建用戶交互的某種超極神祕的方法,我們將使用非常簡單的請求和非常簡單的響應。聽起來似乎沒有多少吸引力,但是用好該對象可以徹底改變您的應用程序。
簡單的 new

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

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

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

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

<script language="javascript" type="text/javascript">
var request = false;
try {
  request = new XMLHttpRequest();
} catch (failed) {
  request = false;
}
if (!request)
  alert("Error initializing XMLHttpRequest!");
</script>
一定要理解這些步驟:
1、創建一個新變量 request 並賦值 false。後面將使用 false 作爲判定條件,它表示還沒有創建 XMLHttpRequest 對象。
2、增加 try/catch 塊:
     1)嘗試創建 XMLHttpRequest 對象。
     2)如果失敗(catch (failed))則保證 request 的值仍然爲 false。
3、檢查 request 是否仍爲 false(如果一切正常就不會是 false)。
4、如果出現問題(request 是 false)則使用 JavaScript 警告通知用戶出現了問題。
代碼非常簡單,對大多數 JavaScript 和 Web 開發人員來說,真正理解它要比讀寫代碼花更長的時間。現在已經得到了一段帶有錯誤檢查的 XMLHttpRequest 對象創建代碼,還可以告訴您哪兒出了問題。
應付 Microsoft

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

[img]mhtml:file://C:/Documents and Settings/Administrator/桌面/四天學會AJAX/[四天學會ajax] 學習Ajax教程第二天,JavaScript 和 Ajax 發出異步請求-Ajax基礎教程 by alixixi_com.mht!http://www.blueidea.com/articleimg/2006/04/3395/ie_broken.jpg[/img]

顯然有什麼地方不對勁,而 Internet Explorer 很難說是一種過時的瀏覽器,因爲全世界有 70% 在使用 Internet Explorer。換句話說,如果不支持 Microsoft 和 Internet Explorer 就不會受到 Web 世界的歡迎!因此我們需要採用不同的方法處理 Microsoft 瀏覽器。
經驗證發現 Microsoft 支持 Ajax,但是其 XMLHttpRequest 版本有不同的稱呼。事實上,它將其稱爲幾種 不同的東西。如果使用較新版本的 Internet Explorer,則需要使用對象 Msxml2.XMLHTTP,而較老版本的 Internet Explorer 則使用 Microsoft.XMLHTTP。我們需要支持這兩種對象類型(同時還要支持非 Microsoft 瀏覽器)。請看看 清單 4,它在前述代碼的基礎上增加了對 Microsoft 的支持。
Microsoft 參與了嗎?
關於 Ajax 和 Microsoft 對該領域不斷增長的興趣和參與已經有很多文章進行了介紹。事實上,據說 Microsoft 最新版本的 Internet Explorer —— version 7.0,將在 2006 年下半年推出 —— 將開始直接支持 XMLHttpRequest,讓您使用 new 關鍵字代替所有的 Msxml2.XMLHTTP 創建代碼。但不要太激動,仍然需要支持舊的瀏覽器,因此跨瀏覽器代碼不會很快消失。
清單 4. 增加對 Microsoft 瀏覽器的支持

<script language="javascript" type="text/javascript">
var request = false;
try {
  request = new XMLHttpRequest();
} catch (trymicrosoft) {
  try {
    request = new ActiveXObject("Msxml2.XMLHTTP");
  } catch (othermicrosoft) {
    try {
      request = new ActiveXObject("Microsoft.XMLHTTP");
    } catch (failed) {
      request = false;
    }
  }
}
if (!request)
  alert("Error initializing XMLHttpRequest!");
</script>

很容易被這些花括號迷住了眼睛,因此下面分別介紹每一步:
1、創建一個新變量 request 並賦值 false。使用 false 作爲判斷條件,它表示還沒有創建 XMLHttpRequest 對象。
2、增加 try/catch 塊:
    1)嘗試創建 XMLHttpRequest 對象。
    2)如果失敗(catch (trymicrosoft)):
            1>嘗試使用較新版本的 Microsoft 瀏覽器創建 Microsoft 兼容的對象(Msxml2.XMLHTTP)。
            2> 如果失敗(catch (othermicrosoft))嘗試使用較老版本的 Microsoft 瀏覽器創建 Microsoft 兼容的對象(Microsoft.XMLHTTP)。
    2)如果失敗(catch (failed))則保證 request 的值仍然爲 false。
3、檢查 request 是否仍然爲 false(如果一切順利就不會是 false)。
4、如果出現問題(request 是 false)則使用 JavaScript 警告通知用戶出現了問題。
這樣修改代碼之後再使用 Internet Explorer 試驗,就應該看到已經創建的表單(沒有錯誤消息)。我實驗的結果如 圖 2 所示。
圖 2. Internet Explorer 正常工作

[img]mhtml:file://C:/Documents and Settings/Administrator/桌面/四天學會AJAX/[四天學會ajax] 學習Ajax教程第二天,JavaScript 和 Ajax 發出異步請求-Ajax基礎教程 by alixixi_com.mht!http://www.blueidea.com/articleimg/2006/04/3395/ie_fixed.jpg[/img]

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

<script language="javascript" type="text/javascript">
var request;
function createRequest() {
  try {
    request = new XMLHttpRequest();
  } catch (trymicrosoft) {
    try {
      request = new ActiveXObject("Msxml2.XMLHTTP");
    } catch (othermicrosoft) {
      try {
        request = new ActiveXObject("Microsoft.XMLHTTP");
      } catch (failed) {
        request = false;
      }
    }
  }
  if (!request)
    alert("Error initializing XMLHttpRequest!");
}
</script>

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

<script language="javascript" type="text/javascript">
var request;
function createRequest() {
  try {
    request = new XMLHttpRequest();
  } catch (trymicrosoft) {
    try {
      request = new ActiveXObject("Msxml2.XMLHTTP");
    } catch (othermicrosoft) {
      try {
        request = new ActiveXObject("Microsoft.XMLHTTP");
      } catch (failed) {
        request = false;
      }
    }
  }
  if (!request)
    alert("Error initializing XMLHttpRequest!");
}
function getCustomerInfo() {
  createRequest();
  // Do something with the request variable
}
</script>

此代碼惟一的問題是推遲了錯誤通知,這也是多數 Ajax 程序員不採用這一方法的原因。假設一個複雜的表單有 10 或 15 個字段、選擇框等,當用戶在第 14 個字段(按照表單順序從上到下)輸入文本時要激活某些 Ajax 代碼。這時候運行 getCustomerInfo() 嘗試創建一個 XMLHttpRequest 對象,但(對於本例來說)失敗了。然後向用戶顯示一條警告,明確地告訴他們不能使用該應用程序。但用戶已經花費了很多時間在表單中輸入數據!這是非常令人討厭的,而討厭顯然不會吸引用戶再次訪問您的網站。
如果使用靜態 JavaScript,用戶在點擊頁面的時候很快就會看到錯誤信息。這樣也很煩人,是不是?可能令用戶錯誤地認爲您的 Web 應用程序不能在他的瀏覽器上運行。不過,當然要比他們花費了 10 分鐘輸入信息之後再顯示同樣的錯誤要好。因此,我建議編寫靜態的代碼,讓用戶儘可能早地發現問題。
用 XMLHttpRequest 發送請求
得到請求對象之後就可以進入請求/響應循環了。記住,XMLHttpRequest 惟一的目的是讓您發送請求和接收響應。其他一切都是 JavaScript、CSS 或頁面中其他代碼的工作:改變用戶界面、切換圖像、解釋服務器返回的數據。準備好 XMLHttpRequest 之後,就可以向服務器發送請求了。
歡迎使用沙箱
Ajax 採用一種沙箱安全模型。因此,Ajax 代碼(具體來說就是 XMLHttpRequest 對象)只能對所在的同一個域發送請求。以後的文章中將進一步介紹安全和 Ajax,現在只要知道在本地機器上運行的代碼只能對本地機器上的服務器端腳本發送請求。如果讓 Ajax 代碼在 http://www.breakneckpizza.com/ 上運行,則必須 http://www.breakneck.com/ 中運行的腳本發送請求。
設置服務器 URL
首先要確定連接的服務器的 URL。這並不是 Ajax 的特殊要求,但仍然是建立連接所必需的,顯然現在您應該知道如何構造 URL 了。多數應用程序中都會結合一些靜態數據和用戶處理的表單中的數據來構造該 URL。比如,清單 7 中的 JavaScript 代碼獲取電話號碼字段的值並用其構造 URL。
清單 7. 建立請求 URL

<script language="javascript" type="text/javascript">
   var request = false;
   try {
     request = new XMLHttpRequest();
   } catch (trymicrosoft) {
     try {
       request = new ActiveXObject("Msxml2.XMLHTTP");
     } catch (othermicrosoft) {
       try {
         request = new ActiveXObject("Microsoft.XMLHTTP");
       } catch (failed) {
         request = false;
       }  
     }
   }
   if (!request)
     alert("Error initializing XMLHttpRequest!");
   function getCustomerInfo() {
     var phone = document.getElementById("phone").value;
     var url = "/cgi-local/lookupCustomer.php?http://www.alixixi.com/search/index.asp?q=XHTML" target=_blank>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 t</p>
   <div id="address"></div>
   <p>Type your order in here:</p>
   <p><textarea name="order" rows="6" cols="50" id="order"></textarea></p>
   <p><input type="submit" value="Order Pizza" id="submit" /></p>
  </form>
</body>
還要注意,當用戶輸入電話號碼或者改變電話號碼時,將觸發 清單 8 所示的 getCustomerInfo() 方法。該方法取得電話號碼並構造存儲在 url 變量中的 URL 字符串。記住,由於 Ajax 代碼是沙箱型的,因而只能連接到同一個域,實際上 URL 中不需要域名。該例中的腳本名爲 /cgi-local/lookupCustomer.php。最後,電話號碼作爲 GET 參數附加到該腳本中:"http://www.alixixi.com/search/index.asp?q=XHTML" target=_blank>XHTML/Ajax 頁面及其連接腳本之間的網絡和數據傳遞,當調用 open() 方法時將看不到任何通信。不清楚爲何選用了這個名字,但顯然不是一個好的選擇。
通常使用其中的前三個參數。事實上,即使需要異步連接,也應該指定第三個參數爲 “true”。這是默認值,但堅持明確指定請求是異步的還是同步的更容易理解。
將這些結合起來,通常會得到 清單 9 所示的一行代碼。
清單 9. 打開請求
   function getCustomerInfo() {
     var phone = document.getElementById("phone").value;
     var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);
     request.open("GET", url, true);
   }
一旦設置好了 URL,其他就簡單了。多數請求使用 GET 就夠了(後面的文章中將看到需要使用 POST 的情況),再加上 URL,這就是使用 open() 方法需要的全部內容了。
挑戰異步性
本系列的後面一篇文章中,我將用很多時間編寫和使用異步代碼,但是您應該明白爲什麼 open() 的最後一個參數這麼重要。在一般的請求/響應模型中,比如 Web 1.0,客戶機(瀏覽器或者本地機器上運行的代碼)向服務器發出請求。該請求是同步的,換句話說,客戶機等待服務器的響應。當客戶機等待的時候,至少會用某種形式通知您在等待:
·沙漏(特別是 Windows 上)。
·旋轉的皮球(通常在 Mac 機器上)。
·應用程序基本上凍結了,然後過一段時間光標變化了。
這正是 Web 應用程序讓人感到笨拙或緩慢的原因 —— 缺乏真正的交互性。按下按鈕時,應用程序實際上變得不能使用,直到剛剛觸發的請求得到響應。如果請求需要大量服務器處理,那麼等待的時間可能很長(至少在這個多處理器、DSL 沒有等待的世界中是如此)。
而異步請求不 等待服務器響應。發送請求後應用程序繼續運行。用戶仍然可以在 Web 表單中輸入數據,甚至離開表單。沒有旋轉的皮球或者沙漏,應用程序也沒有明顯的凍結。服務器悄悄地響應請求,完成後告訴原來的請求者工作已經結束(具體的辦法很快就會看到)。結果是,應用程序感覺不 那麼遲鈍或者緩慢,而是響應迅速、交互性強,感覺快多了。這僅僅是 Web 2.0 的一部分,但它是很重要的一部分。所有老套的 GUI 組件和 Web 設計範型都不能克服緩慢、同步的請求/響應模型。
發送請求
一旦用 open() 配置好之後,就可以發送請求了。幸運的是,發送請求的方法的名稱要比 open() 適當,它就是 send()。
send() 只有一個參數,就是要發送的內容。但是在考慮這個方法之前,回想一下前面已經通過 URL 本身發送過數據了:
var url = "/cgi-local/lookupCustomer.php?phone").value;
     var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);
     request.open("GET", url, true);
     request.send(null);
   }
指定回調方法
現在我們所做的只有很少一點是新的、革命性的或異步的。必須承認,open() 方法中 “true” 這個小小的關鍵字建立了異步請求。但是除此之外,這些代碼與用 Java servlet 及 JSP、PHP 或 Perl 編程沒有什麼兩樣。那麼 Ajax 和 Web 2.0 最大的祕密是什麼呢?祕密就在於 XMLHttpRequest 的一個簡單屬性 onreadystatechange。
首先一定要理解這些代碼中的流程(如果需要請回顧 清單 10)。建立其請求然後發出請求。此外,因爲是異步請求,所以 JavaScript 方法(例子中的 getCustomerInfo())不會等待服務器。因此代碼將繼續執行,就是說,將退出該方法而把控制返回給表單。用戶可以繼續輸入信息,應用程序不會等待服務器。
這就提出了一個有趣的問題:服務器完成了請求之後會發生什麼?答案是什麼也不發生,至少對現在的代碼而言如此!顯然這樣不行,因此服務器在完成通過 XMLHttpRequest 發送給它的請求處理之後需要某種指示說明怎麼做。
在 JavaScript 中引用函數:
JavaScript 是一種弱類型的語言,可以用變量引用任何東西。因此如果聲明瞭一個函數 updatePage(),JavaScript 也將該函數名看作是一個變量。換句話說,可用變量名 updatePage 在代碼中引用函數。
清單 11. 設置回調方法
   function getCustomerInfo() {
     var phone = document.getElementById("phone").value;
     var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);
     request.open("GET", url, true);
     request.onreadystatechange = updatePage;
     request.send(null);
   }
需要特別注意的是該屬性在代碼中設置的位置 —— 它是在調用 send() 之前 設置的。發送請求之前必須設置該屬性,這樣服務器在回答完成請求之後才能查看該屬性。現在剩下的就只有編寫 updatePage() 方法了,這是本文最後一節要討論的重點。
處理服務器響應
發送請求,用戶高興地使用 Web 表單(同時服務器在處理請求),而現在服務器完成了請求處理。服務器查看 onreadystatechange 屬性確定要調用的方法。除此以外,可以將您的應用程序看作其他應用程序一樣,無論是否異步。換句話說,不一定要採取特殊的動作編寫響應服務器的方法,只需要改變表單,讓用戶訪問另一個 URL 或者做響應服務器需要的任何事情。這一節我們重點討論對服務器的響應和一種典型的動作 —— 即時改變用戶看到的表單中的一部分。
回調和 Ajax
現在我們已經看到如何告訴服務器完成後應該做什麼:將 XMLHttpRequest 對象的 onreadystatechange 屬性設置爲要運行的函數名。這樣,當服務器處理完請求後就會自動調用該函數。也不需要擔心該函數的任何參數。我們從一個簡單的方法開始,如 清單 12 所示。
清單 12. 回調方法的代碼

<script language="javascript" type="text/javascript">
   var request = false;
   try {
     request = new XMLHttpRequest();
   } catch (trymicrosoft) {
     try {
       request = new ActiveXObject("Msxml2.XMLHTTP");
     } catch (othermicrosoft) {
       try {
         request = new ActiveXObject("Microsoft.XMLHTTP");
       } catch (failed) {
         request = false;
       }  
     }
   }
   if (!request)
     alert("Error initializing XMLHttpRequest!");
   function getCustomerInfo() {
     var phone = document.getElementById("phone").value;
     var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);
     request.open("GET", url, true);
     request.onreadystatechange = updatePage;
     request.send(null);
   }
   function updatePage() {
     alert("Server is done!");
   }
</script>

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章