歡迎您光臨本站 註冊首頁

掌握 Dojo 工具包,第 2 部分: XHR 框架與 Dojo

←手機掃碼閱讀     火星人 @ 2014-03-12 , reply:0
  
Ajax 的興起改變了傳統的 B/S 結構應用程序中以頁面為單位的交互模式,Ajax 引入的局部刷新機制帶來了更好的用戶體驗,促使瀏覽器中的頁面開始嚮應用程序發展,Google Mail, Google Reader 是在這種趨勢下誕生的典型應用。 Dojo 提供了基於 XmlHttpRequest 的對象的 XHR 框架來支持非同步 Ajax 調用,另外 Dojo.io 包中提供了兩種瀏覽器與伺服器交互的方式:iframe 隱藏框架和 script 動態腳本,他們是對 XHR 框架的有益補充,本系列 的這一期文章將對這些技術進行介紹。

XmlHttpRequest 對象的思考

在傳統的以頁面為單位的瀏覽器和伺服器交互模式中,每一次伺服器請求都會導致整個頁面的重新載入,即使需要更新的僅僅是頁面的一小部分(比如顯示一個登錄錯誤信息)。 Ajax 技術的出現給頁面帶來了一些變化,其中最直觀的莫過於站點的頁面上出現越來越多的“ loading …”,“正在載入中……”等提示信息,有些忽如一夜春風來,loading 載入處處開的意思。“ loading …”或者“正在載入中……”表示瀏覽器正在與伺服器之間進行交互,交互完成之後,將對頁面進行局部刷新,這種交互模式雖然簡單卻極大的提高了 Web 應用的用戶體驗。實現這種模式的核心就是 XmlHttpRequest(後文簡稱 XHR)對象。

XHR 對象促使越來越多“單一頁面”的 Web 應用的誕生。使用 XHR 對象可以發送非同步 HTTP 請求。因為是非同步,在瀏覽器和伺服器交互的過程中,仍然可以操作頁面。當頁面中有多個進行非同步調用的 XHR 對象時,事情有了質的變化,每一個 XHR 對象都可以獨立於伺服器進行通信,瀏覽器中的頁面彷彿是一個多線程的應用程序。這種多線程非同步調用的特性給 Web 應用的開發帶來了很大的影響,越來越多像 Google Mail 這種“單一頁面”的應用湧現出來,而且大受歡迎。之所以能做到“單一頁面”是因為有很多的 XHR 對象默默地在背後服務,我們可以通過啟用 firebug 來查看每次在 Google Mail 頁面上的操作“生產”了多少個 XHR 對象。

Ajax 資源中心

請訪問 Ajax 資源中心,這是有關 Ajax 編程模型信息的一站式中心,包括很多文檔、教程、論壇、blog、wiki 和新聞。任何 Ajax 的新信息都能在這裡找到。

使用 XHR 對象的另一個好處是可以減少伺服器返回的數據量,進而提升系統的性能。在原有的 B/S 交互模式中,伺服器返回的是粗粒度的 HTML 頁面;使用 XHR 對象之後,伺服器返回的是細粒度的數據,如 HTML,JSON,XML 等,請注意這裡返回的是數據而不是頁面,也就是說只返回需要更新的內容,而不返回已經在頁面上顯示的其他內容,所以每次從伺服器返回的數據量比原來要少。採用 AJAX 技術的 Web 應用在初次載入時花費的時間比較長,但是載入完成之後,其性能比原來的 Web 應用要好很多。

這裡介紹了一些 XmlHttpRequest 對象給 Web 開發帶來的變化,這些變化是 Ajax 技術能夠流行的重要原因,認識這些變化可以幫助開發人員設計、開發高效的 Web 應用。本文並不打算介紹 XmlHttpRequest 的屬性、方法,很多文章在這方面已經做得很好。

XHR 框架

XmlHttpRequest 對象是 Dojo 中的 XHR 框架的基礎,目前主流瀏覽器都已經支持此對象,但是不同瀏覽器上實現方式卻不一樣,IE5、IE6 採用 ActiveX 對象的方式,Firefox 和 Safari 都實現為一個內部對象,所以創建 XHR 對象之前需要先測試瀏覽器的類型,清單 1 展示了最簡單的創建 XHR 對象的代碼。


清單 1
				  function createXHR(){       if (window.XMLHttpRequest) { // Non IE           return new XMLHttpRequest();       }       else if (window.ActiveXObject) { // IE           return new ActiveXObject("Microsoft.XMLHTTP");       }   }

或許是認識到 XHR 對象的重要性,微軟在 IE7 中已經把它實現為一個窗口對象的屬性。但是判斷瀏覽器類型的代碼依然不能消除,因為 IE5, IE6 仍然有大量的使用者。

XHR 對象創建方式不一致是 Dojo 的 XHR 框架誕生的一個原因,更重要的原因是原始 XHR 對象還不夠強大,有些方面不能滿足開發的需要:首先 XHR 對象支持的返回類型有限,原始 XHR 對象只有 responseText 和 responseXML 兩個屬性代表返回的數據,重要的數據交換格式 JSON 就不被支持;其次不能設置 HTTP Request 的超時時間,設置超時時間可以讓客戶端腳本控制請求存在的時間,而不是被動的等待伺服器端的返回。

基於這些問題,Dojo 組織提供了一組函數來支持各種 HTTP 請求,包括 xhrGet,rawXhrPost,xhrPut,rawXhrPut,xhrPut,xhrDelete,這幾個函數與 HTTP 協議中的四種請求是一一對應的,HTTP 四種請求是:Get(讀取),Post(更新),Put(創建),Delete(刪除)。 Dojo 組織的發起者 Alex Russell 把這些跟 XHR 對象相關的函數放在一起稱為 XHR 框架。下面我們來看看 Dojo 是如何創建 XHR 對象的。清單 2 是 Dojo 1.1 中創建 XHR 對象的代碼片段。


清單 2
				  d._XMLHTTP_PROGIDS = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0'];   d._xhrObj= function(){       var http = null;       var last_e = null;       if(!dojo.isIE || !djConfig.ieForceActiveXXhr){           try{ http = new XMLHttpRequest(); }catch(e){}       }       if(!http){           for(var i=0; i<3; ++i){               var progid = dojo._XMLHTTP_PROGIDS[i];               try{                   http = new ActiveXObject(progid);               }catch(e){                   last_e = e;               }               if(http){                   dojo._XMLHTTP_PROGIDS = [progid];                  break;               }           }       }       if(!http){           throw new Error("XMLHTTP not available: "+last_e);       }       return http; // XMLHTTPRequest instance    }

_xhrObj 是 Dojo 創建的 XHR 對象。與清單 1 相比,是不是顯得有點“冗長”?其實不然,雖然多了很多 try-catch 語句,但這些 try-catch 塊保證了即使創建 XHR 對象出錯時,瀏覽器依然不會崩潰,增強了代碼的健壯性;此外,代碼對 IE 瀏覽器的“照顧”周到之至,三個 XHR 對象可能存在的命名空間('Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0')都做了判斷,只有這樣才能保證 XHR 對象在各個不同的瀏覽器能順利“誕生”。從這段代碼也可以看出要編寫健壯、高效的代碼,開發人員必須具有系統性的思維,並能合理使用錯誤處理機制。下面將對 XHR 框架中的每個方法進行介紹。

使用 xhrGet 請求資源

xhrGet 是 XHR 框架中最重要的函數,使用頻率也最高。使用它即可以請求伺服器上的靜態文本資源如 txt、xml 等,也可以獲取動態頁面 php、jsp、asp 等,只要從伺服器返回的是字元數據流即可。首先看一個簡單的例子。


清單 3
				  function helloWorld(){       dojo.xhrGet({           url: "helloworld.txt" ,           handleAs: "txt",           load: function(response, ioArgs){alert(response);},           error: function(error, ioArgs){alert(error.message);}       });   }

函數 helloWorld 調用 dojo.xhrGet 獲取伺服器上與引用此 Javascript 腳本的頁面同一目錄下的 helloworld.txt 文件。伺服器成功返回之後,使用 alert 顯示文件的內容。如果出錯了則使用 alert 顯示錯誤信息。

dojo.xhrGet 的參數是一個 JSON 對象,JSON 對象由很多的屬性 / 值對組成,其中的值可以是任意類型的數據: 整形、字元串、函數……甚至是 JSON 對象,這一點使得 JSON 對象的數據描述能力可以與 XML 匹敵,而且 JSON 對象可以使用“ . ”操作符來直接訪問它的屬性,沒有任何解析的開銷,非常方便。在 Javascript 領域,JSON 大有超越 XML 成為事實上的數據交換標準的趨勢。使用 JSON 對象作為函數參數的情形在 Javascript 中非常普遍,可以看成 Javascript 開發中的一個模式,開發人員應該熟悉它。再回到作為 xhrGet 參數的 JSON 對象,在清單 3 的例子中的,這一對象有四個屬性:

  • url:請求的伺服器資源 url,url 標識的只能是文本文件,而不能是二進位文件。
  • handleAs:返回的數據類型,可以是 text(默認)、json、json-comment-optional, json-comment-filtered、javascript、xml 。 Dojo 將根據 handleAs 設置的數據類型對從伺服器返回的數據進行預處理,再傳給 load 屬性指向的回調函數。
  • load:它的值是一個函數,這個函數在請求的資源成功返回之後被調用,實際上就是一回調函數。
  • error:它的值也是一個回調函數,但是只在 http 請求出錯之後(比如,404 錯誤:請求的資源找不到)才被調用。

load, error 所指向的值即可以像清單 3 中所示的那樣是無名函數,也可以是一個已經定義過的函數名,清單 3 的例子可以修改如下:


清單 4
				  function display(response, ioArgs)   {       alert(response);   }   function helloWorld2(){       dojo.xhrGet({           url: "helloworld.txt" ,       handleAs: "txt",           load:display,           error:display});   }

使用這一方法可以提高代碼的復用率,尤其在有多個 xhrGet 對象需要使用相同的 load 回調函數時。

不管 load 的回調函數是無名函數還是預定義的有名函數,它都包含兩個參數:response 和 ioArgs(注意:這兩個參數的名稱可以任意取,這裡只是使用了兩個常用的名稱。實際上,在 Javascript 中,函數是由函數名唯一聲明的,函數參數可以不出現在函數的聲明中,在函數體內可以使用 arguments 引用函數的實際參數)。

  • response:表示從伺服器端返回的數據,Dojo 已經根據 handleAs 設置的數據類型進行了預處理。
  • ioArgs: 這是一個對象,包含調用 xhrGet 時使用的一些參數。之所以把這些信息放在一個對象中並傳遞給回調函數是為了給回調函數一個執行“上下文”,讓回調函數知道自己屬於哪個 HTTP 請求,請求有哪些參數,返回的數據是什麼類型等。這些信息在調試程序時特別有用。
    • ioArgs.url:請求的 URL,與調用 xhrGet 時設置的值一樣。
    • ioArgs.query:請求中包含的參數, URL 中“ ? ”後面的內容。
    • ioArgs.handAs:如何對返回的數據進行預處理,與調用 xhrGet 時設置的值一樣。
    • ioArgs.xhr: xhrGet 函數使用的 XHR 對象。

前面介紹了 xhrGet 函數以及與它關聯的回調函數,xhrGet 中的 handleAs 的設置決定了如何對伺服器返回的數據進行預處理,表 1 詳細介紹了不同的 handleAs 代表的不同的預處理方式。


表 1 handleAs VS 預處理方式
handleAs 預處理方式
text 默認值,不對返回的數據做任何處理
xml 返回 XHR 對象的 responseXML
javascript 使用 dojo.eval 處理返回的數據,返回處理結果
json 使用 dojo.fromJSon 來處理返回的數據,返回生成的 Json 對象
json-comment-optional 如果有數據包含在註釋符中,則只使用 dojo.fromJSon 處理這部分數據,如果沒有數據包含在註釋符中,則使用 dojo.fromJSon 處理全部數據。
json-comment-filtered 數據應該包含在 /* … */ 中,返回使用 dojo.fromJSon 生成的 Json 對象,如果數據不是包含在註釋符中則不處理。

假設 handleAs 被設置為“ json ”,按照上表,則 load 回調函數的參數 response 為 JSON 對象。如果 handleAs 不是“ json ”,還能不能生成 JSON 對象呢?答案是肯定的,可以把 handleAs 設為“ text ”,那麼返回的是普通的字元串,只要字元串是 JSON 對象的文本形式,則可以簡單地使用 eval() 函數把它轉換為真正的 JSON 對象,而不再需要任何其他的 API 完成轉換工作。


清單 5
				  function jsonDemo() {       response = (          "[{ name: 'Joe', age: '30', gender: 'M'},           { name: 'Chandler', age: '32', gender: 'M'},           { name: 'Rose', age: '31', gender: 'M'}]"      );       json = eval(response);       document.write(json[0].name + "," + json[1].age +           "," +  json[2].gender);   }

清單 5 是一個把文本字元串轉換為 JSON 對象的例子。 response 經過 eval 處理後轉換成一個 JSON 對象數組,最後輸出每個 JSON 對象的一個屬性。調用 jsonDemo 將在瀏覽器輸出:

Joe,32,M

這一部分有很大篇幅跟 JSON 相關,因為 JSON 這種數據交換格式應用越來越廣,希望廣大開發者能更加重視。

開發人員往往在 xhrGet 的 load 回調函數中處理伺服器返回的內容,然後更新頁面。最簡單的更新方法是把經過處理的內容設置為頁面上某一節點的 innnerHTML 屬性。返回的內容中最好不要包含需要立即執行的 javascript 代碼片段,因為這段 javascript 是不起作用的。

使用 xhrGet 提交表單

表單的提交在 Web 應用中必不可少,以前 javascript 應用最廣的地方是做表單的驗證,今天我們知道 javascript 能做的比這遠遠要多。使用 xhrGet 提交表單與請求資源類似,只需要在 xhrGet 的參數對象中增加一個屬性,關聯需要提交的 form 。使用 xhrGet 非同步提交 form 意義重大,在傳統的 B/S 交互模式中,提交 form 則意味著頁面的跳轉,但很多情況下頁面不用跳轉,比如用戶登錄時,用戶名或密碼錯誤,這時不跳轉頁面而是直接給出錯誤提示信息用戶體驗明顯要好得多,清單 6 是使用 xhrGet 提交表單的例子。


清單 6
				  <html>    <body>    <script src="../dojo/dojo.js"      djConfig="parseOnLoad: true"></script>    <script>    function handler(response)    { document.write(response); }       function submitForm(){   	 dojo.xhrGet({   		 form: "loginForm" ,   		 handleAs: "text" ,   		 handle: handler,   		 content: { pwd: "modified" },   		 sync: false   	 });   	 return false;    }    </script>    <form id="loginForm" onsubmit="return submitForm();" action="data.php">   	 <input type="textfield" name="id" />   	 <input type="password" name="pwd" />   	 <input type="submit" name="sub" value="submit" />    </form>    </body>    </html>

在這個例子中我們看到 xhrGet 的一些新的參數。這些參數不是僅針對提交表單的,請求資源時也可以使用。之所以在這裡介紹,是為了達到循序漸進學習的目的。例子中的 data.php 是伺服器端的程序,比較簡單,只包含一行代碼 echo $_POST[“pwd”],用來輸出表單中的密碼欄位。

  • form:需要非同步提交的表單的 id 。只有把它設置成想要非同步提交的表單的 id,並在這個表單的 onsubmit 事件中調用自定義的 submitForm() 函數,才能真正做到非同步提交。注意在 submitForm 函數中最後返回了 false,這是為了阻止系統默認的表單提交事件,讓表單提交非同步進行,如果不返回 false,會引起頁面跳轉。
  • handle:handle 也是一個回調函數,在 xhrGet 返回時被調用,正常和錯誤返回的情況都能處理,可以說是 load 和 error 的混合體,但優先順序比 load 低,只有在沒有設置 load 時才起作用。
  • content:在這裡可以修改來自表單的信息,在清單 6 所示的例子中,就使用這一屬性修改了用戶登錄時輸入的密碼。
  • sync:設置同步還是非同步提交。默認是非同步提交,所以在清單 6 以前的例子中並沒有設置這一屬性。

需要注意的是:雖然表單提交的默認方法是 POST,但當使用 xhrGet 提交時,表單提交方式就自動改為 GET,所有表單的數據都會變成查詢字元串出現在 URL 中。所以在伺服器端只能從查詢字元串中取得這些提交的信息。在 jsp 中是:request.getQueryString(“PWD”),而在 php 中可以使用 $_GET[“PWD”] 在伺服器端獲取表單欄位。

XHR 框架中的其他方法

除了 xhrGet,Dojo 的 XHR 框架還包含 xhrPost,rawXhrPost,xhrPut,rawXhrPut,xhrDelete 。這幾個函數與 xhrGet 類似,使用方法和參數都可以參考 xhrGet 。區別在於他們的 HTTP 請求類型,xhrPost 發送的是 Post 請求,xhrPut 發送的是 Put 請求,xhrDelete 發生的是 Delete 請求。

xhrPost 一般用來發送表單數據,當然 xhrGet 也可以做到,區別是 xhrPost 把表單數據封裝在 HTTP 請求的 Body 部分。在伺服器端只能使用取 POST 信息的方法獲取這些表單數據,假設我們要取清單 6 中的表單的 PWD 密碼框中的數據,在 JSP 中可以是 request.getParameter(“PWD”),在 PHP 中可以是 $_POST[“PWD”] 。

如果使用 xhrDelete 去刪除伺服器上的資源,比如某一文件,因為它表示刪除伺服器上的某一資源,而普通用戶是沒有許可權刪除伺服器上資源的許可權的。所以使用 xhrDelete 方法時,一般回返回 405 錯誤(圖 1 是使用 javascript alert 顯示的錯誤信息),表示“對於請求所標識的資源,不允許使用請求行為中所指定的方法”。


圖 1. 405 錯誤提示

Dojo 提供這些方法的目的當然不是為了方便開發人員增加 / 刪除 / 修改伺服器上的物理資源,而是為了支持 REST 架構風格。 REST 架構風格強調使用標準 HTTP 方法,即前文提到的 Get,Post,Put,Delete 來請求、操作 web 資源,注意不是物理資源。舉個例子,在 REST 架構中,新建訂單,應該使用 Put 方法,而刪除訂單應該使用 Delete 方法,而不像在以前的 Web 應用架構中,開發人員通過額外的參數來確定操作的類型。 Dojo 提供這些方法對 REST 架構風格是很好的支持。





iframe,別樣的思路

除了 XHR 框架,Dojo Core 還提供了一個 io 包,Dojo 的官方說明把他們描述成“高級傳輸層(advanced ajax transport layer)”,由兩個對象組成,dojo.io.iframe 和 dojo.io.script 。 使用 dojo.io.iframe 同樣可以跟伺服器交互,但是它採用了與 XHR 對象不同的實現思路。清單 7 是一個使用 iframe 方式提交表單的例子。


清單 7
				  <html><body>    <script src="../dojo/dojo/dojo.js"      djConfig="parseOnLoad: true"></script>    <script>    dojo.require("dojo.io.iframe" );    function iframeTest(){   	 dojo.io.iframe.send({    		 form: dojo.byId('testForm') ,    		 url: "data.php" ,    		 method: "post" ,   		 handleAs: "html" ,    		 load: function(response, ioArgs) {    			 alert(response);    		 }   	 });    }    </script>    <form id="testForm" method="post" >    Name:<input type="text" name="name" size="40" /><br />    Sex:<input type="radio" name="sex" value="m" >    Male<input type="radio" name="sex" value="f"> Female<br />    <input type="button" name="sub" value="submit" onclick="iframeTest()" />    </form>    </body></html>

從這個例子中可以看出,dojo.io.iframe 的使用方式、參數與 xhrGet 非常相似。其中,from,url,handleAs,load 等在 xhrGet 中也存在,唯一不同的是 method,method 表示 dojo.io.iframe 將以何種 HTTP Method 來發送請求。另外需要注意的一點是 handleAs 參數,dojo.io.iframe 一般使用 html,因為在 iframe 中存的其實是另一個 HTML 頁面。如果 handleAs 設置為其他值,像 json,text 等,則在伺服器端須使用 <textarea></textarea> 把要返回的數據包裝起來,比如 hellow, world 要被包裝成 <textarea>hello,world</textarea>,所以最後存在 iframe 中的是一個文本域(textarea),這個文本域包含了從伺服器端返回的數據。這麼做的原因很簡單,就是為了保持從伺服器返回的數據“一成不變”,因為任何字元數據都可以“安全的”放在 HTML 頁面的文本域中。想像一下,我們是不是可以在文本域中輸入各種字元! dojo.io.iframe 會對 textarea 包裝的數據進行處理:首先把 textarea 標籤去掉,然後把數據轉換為 handleAs 指定的類型傳遞給 handle 中設置的回調函數。

dojo.io.iframe 是如何工作的呢?除了 XHR 對象之外還有什麼方法可以實現表單的非同步提交?其實這一切都很簡單,dojo.io.iframe 首先會創建一個隱藏的 iframe 並插入到父頁面的最後,然後設置此 iframe 的 src 屬性為dojo-module-path/resources/blank.html(dojo-module-path 指 dojo 包所在的目錄),iframe 頁面的 onload 事件的處理函數被設置為父窗體的回調函數。接下來就是在 iframe 頁面中發送請求,並接收伺服器的響應。當 iframe 接收到伺服器的反饋並載入完之後,父窗體的回調函數即被調用。

dojo.io.iframe 還有其他幾個很有用的函數

  • create: function(/*String*/fname, /*String*/onloadstr, /*String?*/uri)dojo.io.iframe.setSrc()

    create 函數用來在頁面中創建 iframe,參數 fname 表示 iframe 的名字,setSrc 和 doc 函數據此引用創建的 iframe,onloadstr 表示 iframe 載入完成後執行的回調函數,uri:iframe 請求的資源。后兩個參數是可選的,當 uri 為空時,將載入dojo-module-path/resources/blank.html。

  • setSrc: function(/*DOMNode*/iframe, /*String*/src, /*Boolean*/replace)

    設置指定的 iframe 的 src 屬性,這將導致 iframe 頁面重新載入。 iframe:需要刷新的 iframe 的名字,src:用來刷新 iframe 的頁面 , replace:是否使用 location.replace 方法來更新 iframe 頁面的 url,如果使用 location.replace 方法,則不會在瀏覽器上留下歷史記錄。

  • doc: function(/*DOMNode*/iframeNode)

    獲取指定的 iframe 頁面的 DOM 根節點,有了它可以對 iframe 頁面進行任意的操作。

dojo.io.iframe 採用了不同的思路實現了“非同步”發送請求,但是 dojo.io.iframe 使用並不多,因為當頁面中多處需要非同步通信時,在頁面中創建很多的 iframe 並不是好的注意。在能使用 xhr 框架的地方盡量使用 xhr 框架,唯一值得使用 iframe 的地方是發送文件。





動態 Script,跨域訪問

XHR 框架中的函數功能強大,使用方便。但是 XHR 框架的函數有一問題就是不能跨域訪問,瀏覽器不允許 XHR 對象訪問其他域的站點。比如有一個頁面屬於 a.com,在這個頁面中使用 XHR 對象去訪問 b.com 的某一頁面,這是被禁止的。如果使用 XHR 對象來做跨域訪問一般需要伺服器端的程序做“中轉”,先由伺服器端的程序獲取其他域的數據,然後瀏覽器再使用 XHR 對象從伺服器上獲取這些數據,這種方式即增加了伺服器端的開銷,瀏覽器端的效率也不高。

有沒有方法直接在瀏覽器中實現跨域訪問呢?當然有,它就是 script 標籤,使用 script 標籤可以引用本域或其他域的文件,只要這些文件最後返回的是 javascript 。返回的 javascript 會立即在瀏覽器中執行,執行結果存儲在本地瀏覽器。這一點很重要,它使得各站點可以通過 javascript 來發布自己的服務,像 google 的很多的服務都是通過這種方式提供的。 script 標籤不僅可以靜態添加到頁面中,也可以被動態插入到頁面中,而且通過 DOM 操作方式動態插入的 script 標籤具有與靜態 script 標籤一樣的效果,動態 script 標籤引用的 javascript 文件也會被執行(注意:通過 innerHTML 方式插入的 javascript 是不會被執行的,這一點在前文已經介紹過)。


清單 8
				  function createScript() {       var element = document.createElement("script" );       element.type = "text/javascript" ;       element.src = url;       document.getElementsByTagName("head" )[0].appendChild(element);    }

清單 8 中的例子展示了動態創建 script 標籤的例子,script 標籤即可以放在頁面的 head 部分,也可以放在 body 部分。

動態插入 script 標籤的一個問題是如何判斷返回的 Javascript 執行完了,只有在執行完之後才能引用 Javascript 中的對象、變數、調用它中間的函數等。最簡單的方法是“標誌變數法”,即在腳本中插入標誌變數,在腳本最後給這個變數賦值,而在瀏覽器的腳本中判斷這一變數是否已經被賦值,如果已經被賦值則表示返回的腳本已經執行完。但是這種方法缺點也很明顯,首先如果一個頁面有很多的動態 script 標籤,而每個 script 標籤引用的 javascript 都使用一個標誌變數,那就有很多變數需要判斷,而且這些變數的命名可能衝突,因為這些 Javascript 是由不同的組織、公司提供的,難保不產生衝突。另外在瀏覽器本地腳本中需要輪詢這些變數的值,雖然可以實現,但實在不是高明的做法。目前被廣泛使用的是另一種方法:JSONP(JSON with Padding)。 JSON 表示返回的 Javascript 其實就是一 JSON 對象,這是使用 JSONP 這種方式的前提條件。 Padding 表示在 JSON 對象前要附加上一些東西,究竟是什麼呢?請往下看!

JSONP 的思路很簡單,與其讓瀏覽器腳本來判斷返回的 Javascript 是否執行完畢,不如讓 Javascript 在執行完畢之後自動調用我們想要執行的函數。是不是想起了學習面向對象設計中的“依賴倒置”原則時的那句名言:“Don't call us, we will call you ”。使用 JSONP 這種方法,只需要在原來的 Javascript 引用鏈接上加上一個參數,把需要執行的回調函數傳遞進去。請看下面的兩個 script 標籤。

  • <script src= ” http://url/js.php?parameter= … . ” >
  • <script src= ” http://url/js.php? parameter= … .&callbackname=mycallback” >

如果 http://url/js.php?parameter= … . 返回的是 JSON 對象 {result:“hello,world”} , 那麼第二個 script 標籤返回的則是 mycallback({result: ” hello, world ” }),這一函數將在寫入到瀏覽器后立即被執行,這不就實現了在 Javascript 執行完之後自動調用我們需要執行的回調函數了嗎?

介紹了這麼多動態腳本的背景知識,終於來到了 Dojo 對動態腳本的支持上。 Dojo 即支持標誌變數法,也支持 JSONP 方式。清單 9 是使用標誌變數法的動態 script 的例子,在這個例子中使用了 dojo.io.script.get 函數。


清單 9
				  <html>    <head>    <script src="../dojo/dojo/dojo.js"      djConfig="parseOnLoad: true"></script>    dojo.require("dojo.io.script" );    </head>    <body>    <script>   	 dojo.io.script.get({   		 url:"data.php" ,checkStrin  				g:"test_01" ,   		 handle:function(response, ioArgs) {   			 greetFromServer("hello, world");   		 }   	 });    </script>    </body>    </html>

dojo.io.script.get 函數的使用方式和參數是不是與 xhrGet 很相似?只有 checkString 是 xhrGet 所特有的,checkString 正是“標誌變數法”的關鍵,checkString 表示從伺服器返回的 javascript 需要定義的變數。清單 10 展示了使用 PHP 編寫的伺服器端的腳本,它輸出了一段 javascript,在這段 javasript 的最後給變數 test_01 賦值。而清單 9 中 dojo.io.script.get 函數的 handle 指向的回調函數又調用了這段 javascript 中定義的函數 greetFromServer() 。只有在 test_01 被賦值后,調用 greetFromServer 才是安全的。


清單 10
				  <?php       echo "vartest_01;";       echo "function greetFromServer(name) {alert(name)}";       echo "test_01='defined'";    ?>

dojo.io.script.get 函數也支持 JSONP 方式,當 dojo.io.script.get 函數的參數對象使用了 callbackParamName 屬性時,表示它工作在 JSONP 方式下。 callbackParamName 表示在 url 中添加回調函數名的參數名稱,有點拗口,但是看了下面 dojo.io.script.get 函數在頁面中動態創建的 script 標籤一切就都清楚了,最終出現在 URL 中的是 callbackName,而不是 callbackParamName 。

<script src="data2.php?callbackName=dojo.io.script.jsonp_dojoIoScript1._jsonpCallback" >


清單 11
				  <html><head>    <script src="../dojo/dojo/dojo.js"      djConfig="parseOnLoad: true"></script>    dojo.require("dojo.io.script" );    </head>    <body>    <script>    dojo.io.script.get({   		 url:"data2.php",   		 callbackParamName: "callbackName" ,   		 handle: function(response, ioArgs) {   			 document.write(response.greet);   		 }   	 });    }    </script>    </body></html>

Dojo 會自動創建一個名為 dojo.io.script.jsonp_dojoIoScript1._jsonpCallback 的 javascript 函數,這個函數其實什麼都不做,只是作為一個回調函數傳給伺服器端程序。 php 的伺服器端程序如清單 12 所示,callbackName 像瀏覽器和伺服器之間的一個“信令”,伺服器端必須返回對 callbackName 所代表的函數的調用,因為 Dojo 會檢查它是否被調用過。

所以伺服器端返回的是 dojo.io.script.jsonp_dojoIoScript1._jsonpCallback({greet:’hello, world’}) 。參數 {greet:’hello, world’} 正是要返回到瀏覽器的 JSON 對象。


清單 12
				  <?php       $cb = 
Ajax 的興起改變了傳統的 B/S 結構應用程序中以頁面為單位的交互模式,Ajax 引入的局部刷新機制帶來了更好的用戶體驗,促使瀏覽器中的頁面開始嚮應用程序發展,Google Mail, Google Reader 是在這種趨勢下誕生的典型應用。 Dojo 提供了基於 XmlHttpRequest 的對象的 XHR 框架來支持非同步 Ajax 調用,另外 Dojo.io 包中提供了兩種瀏覽器與伺服器交互的方式:iframe 隱藏框架和 script 動態腳本,他們是對 XHR 框架的有益補充,本系列 的這一期文章將對這些技術進行介紹。

XmlHttpRequest 對象的思考

在傳統的以頁面為單位的瀏覽器和伺服器交互模式中,每一次伺服器請求都會導致整個頁面的重新載入,即使需要更新的僅僅是頁面的一小部分(比如顯示一個登錄錯誤信息)。 Ajax 技術的出現給頁面帶來了一些變化,其中最直觀的莫過於站點的頁面上出現越來越多的“ loading …”,“正在載入中……”等提示信息,有些忽如一夜春風來,loading 載入處處開的意思。“ loading …”或者“正在載入中……”表示瀏覽器正在與伺服器之間進行交互,交互完成之後,將對頁面進行局部刷新,這種交互模式雖然簡單卻極大的提高了 Web 應用的用戶體驗。實現這種模式的核心就是 XmlHttpRequest(後文簡稱 XHR)對象。

XHR 對象促使越來越多“單一頁面”的 Web 應用的誕生。使用 XHR 對象可以發送非同步 HTTP 請求。因為是非同步,在瀏覽器和伺服器交互的過程中,仍然可以操作頁面。當頁面中有多個進行非同步調用的 XHR 對象時,事情有了質的變化,每一個 XHR 對象都可以獨立於伺服器進行通信,瀏覽器中的頁面彷彿是一個多線程的應用程序。這種多線程非同步調用的特性給 Web 應用的開發帶來了很大的影響,越來越多像 Google Mail 這種“單一頁面”的應用湧現出來,而且大受歡迎。之所以能做到“單一頁面”是因為有很多的 XHR 對象默默地在背後服務,我們可以通過啟用 firebug 來查看每次在 Google Mail 頁面上的操作“生產”了多少個 XHR 對象。

Ajax 資源中心

請訪問 Ajax 資源中心,這是有關 Ajax 編程模型信息的一站式中心,包括很多文檔、教程、論壇、blog、wiki 和新聞。任何 Ajax 的新信息都能在這裡找到。

使用 XHR 對象的另一個好處是可以減少伺服器返回的數據量,進而提升系統的性能。在原有的 B/S 交互模式中,伺服器返回的是粗粒度的 HTML 頁面;使用 XHR 對象之後,伺服器返回的是細粒度的數據,如 HTML,JSON,XML 等,請注意這裡返回的是數據而不是頁面,也就是說只返回需要更新的內容,而不返回已經在頁面上顯示的其他內容,所以每次從伺服器返回的數據量比原來要少。採用 AJAX 技術的 Web 應用在初次載入時花費的時間比較長,但是載入完成之後,其性能比原來的 Web 應用要好很多。

這裡介紹了一些 XmlHttpRequest 對象給 Web 開發帶來的變化,這些變化是 Ajax 技術能夠流行的重要原因,認識這些變化可以幫助開發人員設計、開發高效的 Web 應用。本文並不打算介紹 XmlHttpRequest 的屬性、方法,很多文章在這方面已經做得很好。

XHR 框架

XmlHttpRequest 對象是 Dojo 中的 XHR 框架的基礎,目前主流瀏覽器都已經支持此對象,但是不同瀏覽器上實現方式卻不一樣,IE5、IE6 採用 ActiveX 對象的方式,Firefox 和 Safari 都實現為一個內部對象,所以創建 XHR 對象之前需要先測試瀏覽器的類型,清單 1 展示了最簡單的創建 XHR 對象的代碼。


清單 1
				  function createXHR(){       if (window.XMLHttpRequest) { // Non IE           return new XMLHttpRequest();       }       else if (window.ActiveXObject) { // IE           return new ActiveXObject("Microsoft.XMLHTTP");       }   }

或許是認識到 XHR 對象的重要性,微軟在 IE7 中已經把它實現為一個窗口對象的屬性。但是判斷瀏覽器類型的代碼依然不能消除,因為 IE5, IE6 仍然有大量的使用者。

XHR 對象創建方式不一致是 Dojo 的 XHR 框架誕生的一個原因,更重要的原因是原始 XHR 對象還不夠強大,有些方面不能滿足開發的需要:首先 XHR 對象支持的返回類型有限,原始 XHR 對象只有 responseText 和 responseXML 兩個屬性代表返回的數據,重要的數據交換格式 JSON 就不被支持;其次不能設置 HTTP Request 的超時時間,設置超時時間可以讓客戶端腳本控制請求存在的時間,而不是被動的等待伺服器端的返回。

基於這些問題,Dojo 組織提供了一組函數來支持各種 HTTP 請求,包括 xhrGet,rawXhrPost,xhrPut,rawXhrPut,xhrPut,xhrDelete,這幾個函數與 HTTP 協議中的四種請求是一一對應的,HTTP 四種請求是:Get(讀取),Post(更新),Put(創建),Delete(刪除)。 Dojo 組織的發起者 Alex Russell 把這些跟 XHR 對象相關的函數放在一起稱為 XHR 框架。下面我們來看看 Dojo 是如何創建 XHR 對象的。清單 2 是 Dojo 1.1 中創建 XHR 對象的代碼片段。


清單 2
				  d._XMLHTTP_PROGIDS = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0'];   d._xhrObj= function(){       var http = null;       var last_e = null;       if(!dojo.isIE || !djConfig.ieForceActiveXXhr){           try{ http = new XMLHttpRequest(); }catch(e){}       }       if(!http){           for(var i=0; i<3; ++i){               var progid = dojo._XMLHTTP_PROGIDS[i];               try{                   http = new ActiveXObject(progid);               }catch(e){                   last_e = e;               }               if(http){                   dojo._XMLHTTP_PROGIDS = [progid];                  break;               }           }       }       if(!http){           throw new Error("XMLHTTP not available: "+last_e);       }       return http; // XMLHTTPRequest instance    }

_xhrObj 是 Dojo 創建的 XHR 對象。與清單 1 相比,是不是顯得有點“冗長”?其實不然,雖然多了很多 try-catch 語句,但這些 try-catch 塊保證了即使創建 XHR 對象出錯時,瀏覽器依然不會崩潰,增強了代碼的健壯性;此外,代碼對 IE 瀏覽器的“照顧”周到之至,三個 XHR 對象可能存在的命名空間('Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0')都做了判斷,只有這樣才能保證 XHR 對象在各個不同的瀏覽器能順利“誕生”。從這段代碼也可以看出要編寫健壯、高效的代碼,開發人員必須具有系統性的思維,並能合理使用錯誤處理機制。下面將對 XHR 框架中的每個方法進行介紹。

使用 xhrGet 請求資源

xhrGet 是 XHR 框架中最重要的函數,使用頻率也最高。使用它即可以請求伺服器上的靜態文本資源如 txt、xml 等,也可以獲取動態頁面 php、jsp、asp 等,只要從伺服器返回的是字元數據流即可。首先看一個簡單的例子。


清單 3
				  function helloWorld(){       dojo.xhrGet({           url: "helloworld.txt" ,           handleAs: "txt",           load: function(response, ioArgs){alert(response);},           error: function(error, ioArgs){alert(error.message);}       });   }

函數 helloWorld 調用 dojo.xhrGet 獲取伺服器上與引用此 Javascript 腳本的頁面同一目錄下的 helloworld.txt 文件。伺服器成功返回之後,使用 alert 顯示文件的內容。如果出錯了則使用 alert 顯示錯誤信息。

dojo.xhrGet 的參數是一個 JSON 對象,JSON 對象由很多的屬性 / 值對組成,其中的值可以是任意類型的數據: 整形、字元串、函數……甚至是 JSON 對象,這一點使得 JSON 對象的數據描述能力可以與 XML 匹敵,而且 JSON 對象可以使用“ . ”操作符來直接訪問它的屬性,沒有任何解析的開銷,非常方便。在 Javascript 領域,JSON 大有超越 XML 成為事實上的數據交換標準的趨勢。使用 JSON 對象作為函數參數的情形在 Javascript 中非常普遍,可以看成 Javascript 開發中的一個模式,開發人員應該熟悉它。再回到作為 xhrGet 參數的 JSON 對象,在清單 3 的例子中的,這一對象有四個屬性:

  • url:請求的伺服器資源 url,url 標識的只能是文本文件,而不能是二進位文件。
  • handleAs:返回的數據類型,可以是 text(默認)、json、json-comment-optional, json-comment-filtered、javascript、xml 。 Dojo 將根據 handleAs 設置的數據類型對從伺服器返回的數據進行預處理,再傳給 load 屬性指向的回調函數。
  • load:它的值是一個函數,這個函數在請求的資源成功返回之後被調用,實際上就是一回調函數。
  • error:它的值也是一個回調函數,但是只在 http 請求出錯之後(比如,404 錯誤:請求的資源找不到)才被調用。

load, error 所指向的值即可以像清單 3 中所示的那樣是無名函數,也可以是一個已經定義過的函數名,清單 3 的例子可以修改如下:


清單 4
				  function display(response, ioArgs)   {       alert(response);   }   function helloWorld2(){       dojo.xhrGet({           url: "helloworld.txt" ,       handleAs: "txt",           load:display,           error:display});   }

使用這一方法可以提高代碼的復用率,尤其在有多個 xhrGet 對象需要使用相同的 load 回調函數時。

不管 load 的回調函數是無名函數還是預定義的有名函數,它都包含兩個參數:response 和 ioArgs(注意:這兩個參數的名稱可以任意取,這裡只是使用了兩個常用的名稱。實際上,在 Javascript 中,函數是由函數名唯一聲明的,函數參數可以不出現在函數的聲明中,在函數體內可以使用 arguments 引用函數的實際參數)。

  • response:表示從伺服器端返回的數據,Dojo 已經根據 handleAs 設置的數據類型進行了預處理。
  • ioArgs: 這是一個對象,包含調用 xhrGet 時使用的一些參數。之所以把這些信息放在一個對象中並傳遞給回調函數是為了給回調函數一個執行“上下文”,讓回調函數知道自己屬於哪個 HTTP 請求,請求有哪些參數,返回的數據是什麼類型等。這些信息在調試程序時特別有用。
    • ioArgs.url:請求的 URL,與調用 xhrGet 時設置的值一樣。
    • ioArgs.query:請求中包含的參數, URL 中“ ? ”後面的內容。
    • ioArgs.handAs:如何對返回的數據進行預處理,與調用 xhrGet 時設置的值一樣。
    • ioArgs.xhr: xhrGet 函數使用的 XHR 對象。

前面介紹了 xhrGet 函數以及與它關聯的回調函數,xhrGet 中的 handleAs 的設置決定了如何對伺服器返回的數據進行預處理,表 1 詳細介紹了不同的 handleAs 代表的不同的預處理方式。


表 1 handleAs VS 預處理方式
handleAs 預處理方式
text 默認值,不對返回的數據做任何處理
xml 返回 XHR 對象的 responseXML
javascript 使用 dojo.eval 處理返回的數據,返回處理結果
json 使用 dojo.fromJSon 來處理返回的數據,返回生成的 Json 對象
json-comment-optional 如果有數據包含在註釋符中,則只使用 dojo.fromJSon 處理這部分數據,如果沒有數據包含在註釋符中,則使用 dojo.fromJSon 處理全部數據。
json-comment-filtered 數據應該包含在 /* … */ 中,返回使用 dojo.fromJSon 生成的 Json 對象,如果數據不是包含在註釋符中則不處理。

假設 handleAs 被設置為“ json ”,按照上表,則 load 回調函數的參數 response 為 JSON 對象。如果 handleAs 不是“ json ”,還能不能生成 JSON 對象呢?答案是肯定的,可以把 handleAs 設為“ text ”,那麼返回的是普通的字元串,只要字元串是 JSON 對象的文本形式,則可以簡單地使用 eval() 函數把它轉換為真正的 JSON 對象,而不再需要任何其他的 API 完成轉換工作。


清單 5
				  function jsonDemo() {       response = (          "[{ name: 'Joe', age: '30', gender: 'M'},           { name: 'Chandler', age: '32', gender: 'M'},           { name: 'Rose', age: '31', gender: 'M'}]"      );       json = eval(response);       document.write(json[0].name + "," + json[1].age +           "," +  json[2].gender);   }

清單 5 是一個把文本字元串轉換為 JSON 對象的例子。 response 經過 eval 處理後轉換成一個 JSON 對象數組,最後輸出每個 JSON 對象的一個屬性。調用 jsonDemo 將在瀏覽器輸出:

Joe,32,M

這一部分有很大篇幅跟 JSON 相關,因為 JSON 這種數據交換格式應用越來越廣,希望廣大開發者能更加重視。

開發人員往往在 xhrGet 的 load 回調函數中處理伺服器返回的內容,然後更新頁面。最簡單的更新方法是把經過處理的內容設置為頁面上某一節點的 innnerHTML 屬性。返回的內容中最好不要包含需要立即執行的 javascript 代碼片段,因為這段 javascript 是不起作用的。

使用 xhrGet 提交表單

表單的提交在 Web 應用中必不可少,以前 javascript 應用最廣的地方是做表單的驗證,今天我們知道 javascript 能做的比這遠遠要多。使用 xhrGet 提交表單與請求資源類似,只需要在 xhrGet 的參數對象中增加一個屬性,關聯需要提交的 form 。使用 xhrGet 非同步提交 form 意義重大,在傳統的 B/S 交互模式中,提交 form 則意味著頁面的跳轉,但很多情況下頁面不用跳轉,比如用戶登錄時,用戶名或密碼錯誤,這時不跳轉頁面而是直接給出錯誤提示信息用戶體驗明顯要好得多,清單 6 是使用 xhrGet 提交表單的例子。


清單 6
				  <html>    <body>    <script src="../dojo/dojo.js"      djConfig="parseOnLoad: true"></script>    <script>    function handler(response)    { document.write(response); }       function submitForm(){   	 dojo.xhrGet({   		 form: "loginForm" ,   		 handleAs: "text" ,   		 handle: handler,   		 content: { pwd: "modified" },   		 sync: false   	 });   	 return false;    }    </script>    <form id="loginForm" onsubmit="return submitForm();" action="data.php">   	 <input type="textfield" name="id" />   	 <input type="password" name="pwd" />   	 <input type="submit" name="sub" value="submit" />    </form>    </body>    </html>

在這個例子中我們看到 xhrGet 的一些新的參數。這些參數不是僅針對提交表單的,請求資源時也可以使用。之所以在這裡介紹,是為了達到循序漸進學習的目的。例子中的 data.php 是伺服器端的程序,比較簡單,只包含一行代碼 echo $_POST[“pwd”],用來輸出表單中的密碼欄位。

  • form:需要非同步提交的表單的 id 。只有把它設置成想要非同步提交的表單的 id,並在這個表單的 onsubmit 事件中調用自定義的 submitForm() 函數,才能真正做到非同步提交。注意在 submitForm 函數中最後返回了 false,這是為了阻止系統默認的表單提交事件,讓表單提交非同步進行,如果不返回 false,會引起頁面跳轉。
  • handle:handle 也是一個回調函數,在 xhrGet 返回時被調用,正常和錯誤返回的情況都能處理,可以說是 load 和 error 的混合體,但優先順序比 load 低,只有在沒有設置 load 時才起作用。
  • content:在這裡可以修改來自表單的信息,在清單 6 所示的例子中,就使用這一屬性修改了用戶登錄時輸入的密碼。
  • sync:設置同步還是非同步提交。默認是非同步提交,所以在清單 6 以前的例子中並沒有設置這一屬性。

需要注意的是:雖然表單提交的默認方法是 POST,但當使用 xhrGet 提交時,表單提交方式就自動改為 GET,所有表單的數據都會變成查詢字元串出現在 URL 中。所以在伺服器端只能從查詢字元串中取得這些提交的信息。在 jsp 中是:request.getQueryString(“PWD”),而在 php 中可以使用 $_GET[“PWD”] 在伺服器端獲取表單欄位。

XHR 框架中的其他方法

除了 xhrGet,Dojo 的 XHR 框架還包含 xhrPost,rawXhrPost,xhrPut,rawXhrPut,xhrDelete 。這幾個函數與 xhrGet 類似,使用方法和參數都可以參考 xhrGet 。區別在於他們的 HTTP 請求類型,xhrPost 發送的是 Post 請求,xhrPut 發送的是 Put 請求,xhrDelete 發生的是 Delete 請求。

xhrPost 一般用來發送表單數據,當然 xhrGet 也可以做到,區別是 xhrPost 把表單數據封裝在 HTTP 請求的 Body 部分。在伺服器端只能使用取 POST 信息的方法獲取這些表單數據,假設我們要取清單 6 中的表單的 PWD 密碼框中的數據,在 JSP 中可以是 request.getParameter(“PWD”),在 PHP 中可以是 $_POST[“PWD”] 。

如果使用 xhrDelete 去刪除伺服器上的資源,比如某一文件,因為它表示刪除伺服器上的某一資源,而普通用戶是沒有許可權刪除伺服器上資源的許可權的。所以使用 xhrDelete 方法時,一般回返回 405 錯誤(圖 1 是使用 javascript alert 顯示的錯誤信息),表示“對於請求所標識的資源,不允許使用請求行為中所指定的方法”。


圖 1. 405 錯誤提示

Dojo 提供這些方法的目的當然不是為了方便開發人員增加 / 刪除 / 修改伺服器上的物理資源,而是為了支持 REST 架構風格。 REST 架構風格強調使用標準 HTTP 方法,即前文提到的 Get,Post,Put,Delete 來請求、操作 web 資源,注意不是物理資源。舉個例子,在 REST 架構中,新建訂單,應該使用 Put 方法,而刪除訂單應該使用 Delete 方法,而不像在以前的 Web 應用架構中,開發人員通過額外的參數來確定操作的類型。 Dojo 提供這些方法對 REST 架構風格是很好的支持。





iframe,別樣的思路

除了 XHR 框架,Dojo Core 還提供了一個 io 包,Dojo 的官方說明把他們描述成“高級傳輸層(advanced ajax transport layer)”,由兩個對象組成,dojo.io.iframe 和 dojo.io.script 。 使用 dojo.io.iframe 同樣可以跟伺服器交互,但是它採用了與 XHR 對象不同的實現思路。清單 7 是一個使用 iframe 方式提交表單的例子。


清單 7
				  <html><body>    <script src="../dojo/dojo/dojo.js"      djConfig="parseOnLoad: true"></script>    <script>    dojo.require("dojo.io.iframe" );    function iframeTest(){   	 dojo.io.iframe.send({    		 form: dojo.byId('testForm') ,    		 url: "data.php" ,    		 method: "post" ,   		 handleAs: "html" ,    		 load: function(response, ioArgs) {    			 alert(response);    		 }   	 });    }    </script>    <form id="testForm" method="post" >    Name:<input type="text" name="name" size="40" /><br />    Sex:<input type="radio" name="sex" value="m" >    Male<input type="radio" name="sex" value="f"> Female<br />    <input type="button" name="sub" value="submit" onclick="iframeTest()" />    </form>    </body></html>

從這個例子中可以看出,dojo.io.iframe 的使用方式、參數與 xhrGet 非常相似。其中,from,url,handleAs,load 等在 xhrGet 中也存在,唯一不同的是 method,method 表示 dojo.io.iframe 將以何種 HTTP Method 來發送請求。另外需要注意的一點是 handleAs 參數,dojo.io.iframe 一般使用 html,因為在 iframe 中存的其實是另一個 HTML 頁面。如果 handleAs 設置為其他值,像 json,text 等,則在伺服器端須使用 <textarea></textarea> 把要返回的數據包裝起來,比如 hellow, world 要被包裝成 <textarea>hello,world</textarea>,所以最後存在 iframe 中的是一個文本域(textarea),這個文本域包含了從伺服器端返回的數據。這麼做的原因很簡單,就是為了保持從伺服器返回的數據“一成不變”,因為任何字元數據都可以“安全的”放在 HTML 頁面的文本域中。想像一下,我們是不是可以在文本域中輸入各種字元! dojo.io.iframe 會對 textarea 包裝的數據進行處理:首先把 textarea 標籤去掉,然後把數據轉換為 handleAs 指定的類型傳遞給 handle 中設置的回調函數。

dojo.io.iframe 是如何工作的呢?除了 XHR 對象之外還有什麼方法可以實現表單的非同步提交?其實這一切都很簡單,dojo.io.iframe 首先會創建一個隱藏的 iframe 並插入到父頁面的最後,然後設置此 iframe 的 src 屬性為dojo-module-path/resources/blank.html(dojo-module-path 指 dojo 包所在的目錄),iframe 頁面的 onload 事件的處理函數被設置為父窗體的回調函數。接下來就是在 iframe 頁面中發送請求,並接收伺服器的響應。當 iframe 接收到伺服器的反饋並載入完之後,父窗體的回調函數即被調用。

dojo.io.iframe 還有其他幾個很有用的函數

  • create: function(/*String*/fname, /*String*/onloadstr, /*String?*/uri)dojo.io.iframe.setSrc()

    create 函數用來在頁面中創建 iframe,參數 fname 表示 iframe 的名字,setSrc 和 doc 函數據此引用創建的 iframe,onloadstr 表示 iframe 載入完成後執行的回調函數,uri:iframe 請求的資源。后兩個參數是可選的,當 uri 為空時,將載入dojo-module-path/resources/blank.html。

  • setSrc: function(/*DOMNode*/iframe, /*String*/src, /*Boolean*/replace)

    設置指定的 iframe 的 src 屬性,這將導致 iframe 頁面重新載入。 iframe:需要刷新的 iframe 的名字,src:用來刷新 iframe 的頁面 , replace:是否使用 location.replace 方法來更新 iframe 頁面的 url,如果使用 location.replace 方法,則不會在瀏覽器上留下歷史記錄。

  • doc: function(/*DOMNode*/iframeNode)

    獲取指定的 iframe 頁面的 DOM 根節點,有了它可以對 iframe 頁面進行任意的操作。

dojo.io.iframe 採用了不同的思路實現了“非同步”發送請求,但是 dojo.io.iframe 使用並不多,因為當頁面中多處需要非同步通信時,在頁面中創建很多的 iframe 並不是好的注意。在能使用 xhr 框架的地方盡量使用 xhr 框架,唯一值得使用 iframe 的地方是發送文件。





動態 Script,跨域訪問

XHR 框架中的函數功能強大,使用方便。但是 XHR 框架的函數有一問題就是不能跨域訪問,瀏覽器不允許 XHR 對象訪問其他域的站點。比如有一個頁面屬於 a.com,在這個頁面中使用 XHR 對象去訪問 b.com 的某一頁面,這是被禁止的。如果使用 XHR 對象來做跨域訪問一般需要伺服器端的程序做“中轉”,先由伺服器端的程序獲取其他域的數據,然後瀏覽器再使用 XHR 對象從伺服器上獲取這些數據,這種方式即增加了伺服器端的開銷,瀏覽器端的效率也不高。

有沒有方法直接在瀏覽器中實現跨域訪問呢?當然有,它就是 script 標籤,使用 script 標籤可以引用本域或其他域的文件,只要這些文件最後返回的是 javascript 。返回的 javascript 會立即在瀏覽器中執行,執行結果存儲在本地瀏覽器。這一點很重要,它使得各站點可以通過 javascript 來發布自己的服務,像 google 的很多的服務都是通過這種方式提供的。 script 標籤不僅可以靜態添加到頁面中,也可以被動態插入到頁面中,而且通過 DOM 操作方式動態插入的 script 標籤具有與靜態 script 標籤一樣的效果,動態 script 標籤引用的 javascript 文件也會被執行(注意:通過 innerHTML 方式插入的 javascript 是不會被執行的,這一點在前文已經介紹過)。


清單 8
				  function createScript() {       var element = document.createElement("script" );       element.type = "text/javascript" ;       element.src = url;       document.getElementsByTagName("head" )[0].appendChild(element);    }

清單 8 中的例子展示了動態創建 script 標籤的例子,script 標籤即可以放在頁面的 head 部分,也可以放在 body 部分。

動態插入 script 標籤的一個問題是如何判斷返回的 Javascript 執行完了,只有在執行完之後才能引用 Javascript 中的對象、變數、調用它中間的函數等。最簡單的方法是“標誌變數法”,即在腳本中插入標誌變數,在腳本最後給這個變數賦值,而在瀏覽器的腳本中判斷這一變數是否已經被賦值,如果已經被賦值則表示返回的腳本已經執行完。但是這種方法缺點也很明顯,首先如果一個頁面有很多的動態 script 標籤,而每個 script 標籤引用的 javascript 都使用一個標誌變數,那就有很多變數需要判斷,而且這些變數的命名可能衝突,因為這些 Javascript 是由不同的組織、公司提供的,難保不產生衝突。另外在瀏覽器本地腳本中需要輪詢這些變數的值,雖然可以實現,但實在不是高明的做法。目前被廣泛使用的是另一種方法:JSONP(JSON with Padding)。 JSON 表示返回的 Javascript 其實就是一 JSON 對象,這是使用 JSONP 這種方式的前提條件。 Padding 表示在 JSON 對象前要附加上一些東西,究竟是什麼呢?請往下看!

JSONP 的思路很簡單,與其讓瀏覽器腳本來判斷返回的 Javascript 是否執行完畢,不如讓 Javascript 在執行完畢之後自動調用我們想要執行的函數。是不是想起了學習面向對象設計中的“依賴倒置”原則時的那句名言:“Don't call us, we will call you ”。使用 JSONP 這種方法,只需要在原來的 Javascript 引用鏈接上加上一個參數,把需要執行的回調函數傳遞進去。請看下面的兩個 script 標籤。

  • <script src= ” http://url/js.php?parameter= … . ” >
  • <script src= ” http://url/js.php? parameter= … .&callbackname=mycallback” >

如果 http://url/js.php?parameter= … . 返回的是 JSON 對象 {result:“hello,world”} , 那麼第二個 script 標籤返回的則是 mycallback({result: ” hello, world ” }),這一函數將在寫入到瀏覽器后立即被執行,這不就實現了在 Javascript 執行完之後自動調用我們需要執行的回調函數了嗎?

介紹了這麼多動態腳本的背景知識,終於來到了 Dojo 對動態腳本的支持上。 Dojo 即支持標誌變數法,也支持 JSONP 方式。清單 9 是使用標誌變數法的動態 script 的例子,在這個例子中使用了 dojo.io.script.get 函數。


清單 9
				  <html>    <head>    <script src="../dojo/dojo/dojo.js"      djConfig="parseOnLoad: true"></script>    dojo.require("dojo.io.script" );    </head>    <body>    <script>   	 dojo.io.script.get({   		 url:"data.php" ,checkStrin  				g:"test_01" ,   		 handle:function(response, ioArgs) {   			 greetFromServer("hello, world");   		 }   	 });    </script>    </body>    </html>

dojo.io.script.get 函數的使用方式和參數是不是與 xhrGet 很相似?只有 checkString 是 xhrGet 所特有的,checkString 正是“標誌變數法”的關鍵,checkString 表示從伺服器返回的 javascript 需要定義的變數。清單 10 展示了使用 PHP 編寫的伺服器端的腳本,它輸出了一段 javascript,在這段 javasript 的最後給變數 test_01 賦值。而清單 9 中 dojo.io.script.get 函數的 handle 指向的回調函數又調用了這段 javascript 中定義的函數 greetFromServer() 。只有在 test_01 被賦值后,調用 greetFromServer 才是安全的。


清單 10
				  <?php       echo "vartest_01;";       echo "function greetFromServer(name) {alert(name)}";       echo "test_01='defined'";    ?>

dojo.io.script.get 函數也支持 JSONP 方式,當 dojo.io.script.get 函數的參數對象使用了 callbackParamName 屬性時,表示它工作在 JSONP 方式下。 callbackParamName 表示在 url 中添加回調函數名的參數名稱,有點拗口,但是看了下面 dojo.io.script.get 函數在頁面中動態創建的 script 標籤一切就都清楚了,最終出現在 URL 中的是 callbackName,而不是 callbackParamName 。

<script src="data2.php?callbackName=dojo.io.script.jsonp_dojoIoScript1._jsonpCallback" >


清單 11
				  <html><head>    <script src="../dojo/dojo/dojo.js"      djConfig="parseOnLoad: true"></script>    dojo.require("dojo.io.script" );    </head>    <body>    <script>    dojo.io.script.get({   		 url:"data2.php",   		 callbackParamName: "callbackName" ,   		 handle: function(response, ioArgs) {   			 document.write(response.greet);   		 }   	 });    }    </script>    </body></html>

Dojo 會自動創建一個名為 dojo.io.script.jsonp_dojoIoScript1._jsonpCallback 的 javascript 函數,這個函數其實什麼都不做,只是作為一個回調函數傳給伺服器端程序。 php 的伺服器端程序如清單 12 所示,callbackName 像瀏覽器和伺服器之間的一個“信令”,伺服器端必須返回對 callbackName 所代表的函數的調用,因為 Dojo 會檢查它是否被調用過。

所以伺服器端返回的是 dojo.io.script.jsonp_dojoIoScript1._jsonpCallback({greet:’hello, world’}) 。參數 {greet:’hello, world’} 正是要返回到瀏覽器的 JSON 對象。


清單 12
___FCKpd___11

清單 11 所示程序的輸出為:hello, world,由此可以看出,response 參數就是從伺服器端返回的 JSON 對象,伺服器端的 JSON 對象終於成功的傳遞到瀏覽器了。前面介紹了這麼多的機制都是為了使這個 JSON 對象安全返回到瀏覽器中。當然你可以在伺服器端返回任何數據,比如直接返回一個字元串,但此時 response 就變成字元串了,當然也就不能再叫 JSONP 了,因為 JSONP 特指返回的是 JSON 對象。

dojo.io.script 對象中除了 get 函數之外,還有 attach,和 remove 兩個函數

  • attach: function(/*String*/id, /*String*/url)

    創建動態 script 標籤,標籤的 id 由參數 id 指定,src 由 url 指定。

  • remove: function(/*String*/id)

    刪除 id 代表 script 標籤。

總結

本文介紹了 Dojo 中三種瀏覽器與伺服器交互的方式,這三種方式各有優缺點,但是在使用方式卻出奇的一致; xhr 框架的函數,dojo.io.iframe、dojo.io.script 對象的函數使用的 JSON 對象參數也極其相似,而且淺顯易懂。 Dojo 設計者的這一良好設計極大的減輕了開發人員的學習負擔,作為框架開發人員應該了解這一理念。表 2 對這三種方式從三個方面進行了比較。


表 2. 三種方式的比較
  支持的 HTTP 請求類型 期望的輸出 跨域訪問
XHR Get, post, delete, put text, json, xml, javascript … N
iframe Get, post html N
script Get javascript Y

綜上所述,使用上述三種方法時需要遵循一條簡單的原則:傳送文件則 iframe,跨域訪問則使用動態腳本,其餘則選 XHR 框架。(責任編輯:A6)

GET["callbackName"]; echo $cb."({greet:'hello, world'});"; ?>

清單 11 所示程序的輸出為:hello, world,由此可以看出,response 參數就是從伺服器端返回的 JSON 對象,伺服器端的 JSON 對象終於成功的傳遞到瀏覽器了。前面介紹了這麼多的機制都是為了使這個 JSON 對象安全返回到瀏覽器中。當然你可以在伺服器端返回任何數據,比如直接返回一個字元串,但此時 response 就變成字元串了,當然也就不能再叫 JSONP 了,因為 JSONP 特指返回的是 JSON 對象。

dojo.io.script 對象中除了 get 函數之外,還有 attach,和 remove 兩個函數

  • attach: function(/*String*/id, /*String*/url)

    創建動態 script 標籤,標籤的 id 由參數 id 指定,src 由 url 指定。

  • remove: function(/*String*/id)

    刪除 id 代表 script 標籤。

總結

本文介紹了 Dojo 中三種瀏覽器與伺服器交互的方式,這三種方式各有優缺點,但是在使用方式卻出奇的一致; xhr 框架的函數,dojo.io.iframe、dojo.io.script 對象的函數使用的 JSON 對象參數也極其相似,而且淺顯易懂。 Dojo 設計者的這一良好設計極大的減輕了開發人員的學習負擔,作為框架開發人員應該了解這一理念。表 2 對這三種方式從三個方面進行了比較。


表 2. 三種方式的比較
  支持的 HTTP 請求類型 期望的輸出 跨域訪問
XHR Get, post, delete, put text, json, xml, javascript … N
iframe Get, post html N
script Get javascript Y

綜上所述,使用上述三種方法時需要遵循一條簡單的原則:傳送文件則 iframe,跨域訪問則使用動態腳本,其餘則選 XHR 框架。(責任編輯:A6)



[火星人 ] 掌握 Dojo 工具包,第 2 部分: XHR 框架與 Dojo已經有797次圍觀

http://coctec.com/docs/linux/show-post-69041.html