DIV CSS 佈局教程網

 DIV+CSS佈局教程網 >> 網頁腳本 >> JavaScript入門知識 >> 關於JavaScript >> 網頁裡做異步的跨域請求
網頁裡做異步的跨域請求
編輯:關於JavaScript     

網頁制作poluoluo文章簡介:這篇文章將會探討一下在網頁裡做異步的跨域請求,以及借助iframe來獲取數據的方法。

這篇文章將會探討一下在網頁裡做異步的跨域請求,以及借助iframe來獲取數據的方法。

呃,本來我覺得這個話題沒什麼好說的了,因為如今好像沒有幾個web應用能離開這類request,google和facebook用iframe來做comet的時候也基本上把能hack的都hack遍了,所以我估計開發者社區裡應該早就形成所謂的”最佳實踐”(best practices)了罷。不過最近看到有一些關注前端技術的blog(比如realazy)在討論相關的話題,發現還是有一些東西值得寫下來。


一、借助script的異步跨域請求

先說跨域的問題,首先要指出的是,iframe裡的js宿主對象一樣也躲不開同源策略(Same Origin Policy),僅僅能解決二級域名的跨域而已,比如www.tudou.com和so.tudou.com,如果要請求某個八桿子打不到一起去的域名下的數據(例如你想搞mashup),建議老老實實的用script標簽去請求JSONP罷。關於JSONP要附帶說一下的是,jQuery對JSONP請求的封裝方式很值得提倡:

  1. $.getJSON(url, params + "&jsoncallback=?", function(json){
  2.     /* do something */
  3. });

用jsoncallback作為服務器端支持的標准jsonp參數,而每次執行這個方法都會用時間戳生成一個唯一的全局函數名,替換這個“?”,這個細節被封裝到黑盒裡,使用者不必了解,可以像普通的ajax請求一樣,用匿名的回調函數作為最末尾的參數(這是jquery強調的風格),這種語法糖(syntactic sugar)的作用絕對不僅僅是讓前端開發人員可以偷懶而已,對代碼的可讀性,兼容性和今後的維護都有好處。(我經常要向服務器端的開發人員解釋這個道理,否則他們才不給你支持什麼jsoncallback參數呢,直接給你返回一個“yy({……})”就算完工……囧)

二、利用前沿技術的跨域方法

當然了,我們還可以走一些目前來看比較野的路子來實現跨域,比如在頁面裡嵌入一個不到1K的swf,借助flashplayer向部署了crossdomain.xml的服務器請求數據,再用actionscript裡的ExternalInterface類把數據還給javascript(我覺得這種方法忒有調用dll的感覺~大心)。

我們還可以指望ie8裡支持的XDomainRequestAllowed,和firefox3.1支持的Access Control,甚至傳說中的HTML5 socket……噢喔喔,多麼甜美的夢……/掐一把臉蛋

三、用iframe直接發送跨域請求

跟script標簽一樣,iframe也可以用來替代ajax,而且在修改document.domain之後(比如上面提到的兩個域名,可以設置document.domain = “tudou.com”),還可以解決部分跨域問題。

通過iframe請求數據的方法,最直接的莫過於在頁面裡動態的嵌入一個iframe標簽,用它的src屬性直接請求包含數據的網頁,然後利用那個網頁裡的js把數據傳給父頁面,比如:

  1. <iframe id="crossdomain" width="0" height="0" style="visibility:hidden;" src="http://yoursite.com/request_url/" ></iframe>

這種方法耦合的太緊,非常不推薦。你請求的URI代表一個資源,應該是單純的數據,它會作為xml,json,js代碼還是html來處理,這個並不重要,不應該把你的程序邏輯跟數據混雜到一起,數據也不應該因為跨域或不跨域,用iframe,script還是ajax來請求就變成完全不同的東西。

有人會說:為了讓數據能夠被JS處理,返回的內容難免有差異。——但是小的差異可以通過合理的封裝隱藏起來,比如JQuery的getJSON方法

有人會說:請求的URI是一個動態頁面,同樣可以在URI裡支持類似jsoncallback的參數,生成一個script標簽和其中的JS代碼,把數據“包裹”在JS裡,比如請求“http://yoursite.com/request_url/?callback=cb1304344”,返回:

  1. <script type="text/javascript">
  2. document.domain="tudou.com";
  3. top.cb1304344({ /* 數據 */ });
  4. </script>

——首先,很多情況下你請求到的不會是動態頁面,在這個到處都強調高負載的web世界裡,你拿到的經常是squid之類的代理程序返回的緩存。其次,如果你請求的是HTML格式的文本,為了能作為JS代碼來執行,服務器端必須對這段文本做轉義和清理工作,而且安全性還不一定能保證(因為HTML裡經常包含很多來自UGC的內容),如果你請求的是JSON格式的數據……那何必用iframe咧……直接嵌script罷……

四、用iframe直接請求數據的最佳實踐

我推薦在上述方法的基礎上做改良,首先在服務器端,直接返回數據本身,並且把數據“包裹”在一個textarea標簽裡,比如:

  1. <textarea><div><p>yyyyy</p></div></textarea>

textarea的優點是可以支持任何格式的內容,而且這些內容不會在iframe子頁面裡解析(比如創建DOM樹,執行JS),接下來前端要做的,只是在父頁面裡獲取到子頁面的DOM,把textarea的內容取出來(注意不能取innerHTML而要取value)。

這裡存在一個判斷iframe是否加載完成的問題,解決方法之一是在iframe標簽上寫onload事件,不過這樣就需要顯式的調用一個函數。

方法二如下:

  1. (function(){
  2. try{
  3.     callback(document.getElementById('crossdomain').contentWindow.document.body.getElementsByTagName("TEXTAREA")[0].value);
  4. }catch(e){
  5.     setTimeout(arguments.callee,0);
  6.     return;
  7. }   
  8. })();

最後我們可以封裝出這樣一個方法:

  1. window.TUI = window.$ = {};
  2. /**
  3. * @public 通過iframe異步請求數據
  4. * @param {string}  url是請求的地址
  5. * @param {function}  cb是處理返回數據的回調函數
  6. */
  7. TUI.getIframeData = function(url, cb){
  8.     var f = document.getElementById('crossdomain');
  9.     if(f)
  10.         f.src = url;
  11.     else{
  12.         var t = document.createElement("DIV");
  13.         t.innerHTML = '<iframe id="crossdomain" width="0" height="0" style="visibility:hidden;" src="' + url + '" ></iframe>';
  14.         document.body.appendChild(t.firstChild);
  15.     }
  16.  
  17.     (function(){
  18.     try{
  19.         cb(document.getElementById('crossdomain').contentWindow.document.body.getElementsByTagName("TEXTAREA")[0].value);
  20.     }catch(e){
  21.         setTimeout(arguments.callee,0);
  22.         return;
  23.     }   
  24.     })();
  25. };
  26.  
  27.  
  28. //像這樣執行
  29. $.getIframeData("http://yoursite.com/request_url/", function(data){
  30.     /* do something */
  31. });

只要再增加一個可選的param參數,這就是一個很標准的jQuery AJAX API,我們還可以在jQuery的$.get上面封裝,增加一個是否跨域的判斷,當這個request的URI修改成同樣的域名後,自動切換到普通的AJAX方法來請求,把返回的文本用類似這樣的正則/<(textarea)>(.+)<\/(textarea)>/刪掉多余的字符,再傳給回調函數,前端和服務器端都不用修改代碼。

五、用iframe直接請求數據的缺陷

必須指出的是,iframe在ie裡獲取數據時會引發一些“小問題”,dojo的創始人Alex Russell把它們稱作“靈異點擊(phantom click)”和“噩夢般的指示器(throbber of doom)”,前者是指在iframe請求內容的時候會出現一次點擊鏈接的音效(讓用戶懷疑鬧鬼,多差的體驗口牙!),後者是指iframe加載過程中,ie的界面上會出現正在讀取的提示(比如左下的進度條,右上的圖標)……好罷,其實以我個人的標准,這兩個問題都可以無視……

這種方法還有一個明顯的缺陷,就是只支持GET類型的請求。

六、用iframe結合ajax

不過iframe還有一種使用方法,不但可以避免上面提到的問題,也不需要服務器端做任何調整,簡單來說:在iframe的src裡調用一個包含ajax方法的頁面,然後父頁面調用這個方法來發起跟子頁面同域名下的ajax請求。在土豆網的播放頁面上,我使用這種方法請求用戶評論統一接口裡的HTML內容,例如這個WH40K:DOWII的視頻:

http://www.tudou.com/programs/view/iPcprDz_LhI/

獲得評論部分HTML的接口類似這樣:

http://comments.tudou.com/itemcomment.srv?method=get&iid=21283123&page=1&tm=5&ban=1

這個接口在獨立的一組服務器上實現,在視頻播放頁,豆單播放頁,豆單封面,相冊,個人主頁都會被調用。由於包含大量用戶提交的內容和復雜的HTML結構,如果用JSON形式,前端後端處理起來都效率低,此外,提交新評論,回復,刪除,也會用到comments.tudou.com這個域名下的接口,而這些操作顯然需要POST類型的請求。在這種需求下,借助iframe的AJAX方法

首先在comments.tudou.com域名下部署一個供iframe調用的跨域文件,感覺很像flashplayer的crossdomain.xml……

http://comments.tudou.com/crossdomain/index.html

可以看到源文件裡僅僅包含一個stand-alone的ajax方法……呃……你覺得很眼熟?不用懷疑,就是在jQuery源代碼的基礎上修改來的-___-b,支持最基本的需求。這個頁面可以設置很長的過期頭讓浏覽器緩存起來,因為不會再有變動。

在父頁面裡通過TUI.videoComment.request提供統一的接口,不做詳述了,只列舉其中訪問跨域方法的部分:

  1. (function(){
  2.     try{
  3.         $('#crossdomain')[0].contentWindow.TUI.ajax(o);   
  4.     }catch(e){
  5.         setTimeout(arguments.callee,500);
  6.         return;
  7.     }   
  8. })();

七、總結一下

iframe適用於 ( 跨域的 && ( 返回大量數據 || 返回HTML內容 || 需要發POST請求 ) ) 的場合,除此之外還有comet裡的串流技術(streaming)——本文不涉及。使用時需要注意保持資源的純粹性,並盡可能隱藏那些跟其他異步請求差異很大的或包含hack的細節(比如嵌入iframe,觸發回調函數,處理數據),設計出一致的,兼容性和擴展性良好的,不礙眼的接口XD

關於跨域還要補充一點:修改document.domain可能會產生一些無法預料的問題,比如在firefox裡,document.styleSheets的cssRules屬性會被拒絕訪問。

XML學習教程| jQuery入門知識| AJAX入門| Dreamweaver教程| Fireworks入門知識| SEO技巧| SEO優化集錦|
Copyright © DIV+CSS佈局教程網 All Rights Reserved