emu in blogjava

            BlogJava :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理 ::
            171 隨筆 :: 103 文章 :: 1052 評論 :: 2 Trackbacks
          如果不用xmlhttp方式獲取json數據,一般我們最好用的方式是用script標簽直接引用需要的腳本。但是不像xmlhttp可以很容易的把請求數據腳本和請求到的數據綁定到一起,script標簽本身是無法獲知自己獲得了什么數據的,這個問題上一般使用的解決方案有:

          1 事先約定前后臺接口。這樣帶來了很強的前后臺偶合,后臺程序需要知道前臺想要做什么,接口很難一致化,一般不同的服務程序要使用不同的接口。而且如果需要同時并發調用同一個服務程序幾次,那么一樣無法解決接口沖突問題。

          2 前臺動態生成回調接口后把接口名稱傳遞給后臺程序,后臺程序根據接受到的接口名稱動態生成回調接口,比如google就喜歡接受callback參數: http://www.google.com/reader/public/javascript/user/10949413115399023739/label/officialgoogleblogs?n=10&callback=test
          飯否的接口也是這樣的:
          http://api.fanfou.com/statuses/user_timeline.json?callback=test
          這樣也是一個無奈之舉,一樣避免不了的令人生厭的前后臺偶合,只是改變了偶合的方式,前后臺需要換一種方式的約定,而且如果要解決并行多個異步回調的接口沖突問題,就要動態的給每個回調函數創建一個個不同的名稱,此外服務程序的輸出不允許靜態化,必須有接受參數和生成回調腳本的功能。

          假如我們想要像生成靜態rss(http://api.fanfou.com/statuses/user_timeline.rss)文件一樣的生成靜態的json(http://api.fanfou.com/statuses/user_timeline.json)又不希望或者不能使用xmlhttp來拉取json字符串,而想要用一致的callback接口來回傳數據,那么怎么樣才能解決接口沖突問題呢?事實上只有做到這點,json才能真正想xml一樣變成一個純粹的數據描述方式,擺脫對具體上下文程序的依賴,讓一個數據自由的被不同目的的頁面mashup。比如說,在一個頁面上用json結合腳本技術,把來自不同網站的相同格式的json數據合并顯示到一個頁面上。

          emu在這個問題上花費過無數心血后最終還是放棄了,直到昨晚,舜子才終于有了突破:

          <HTML>
          <HEAD>
          <SCRIPT LANGUAGE="JavaScript">
          function loadjs(url,callback){
              
          if(window.ActiveXObject){
                  
          var df = document.createDocumentFragment();    
                  df.visitCountCallBack 
          = callback
                  
          var s = document.createElement("script");
                  df.appendChild(s)
                  s.src
          =url;
              }
          else{
                  
          var i = document.createElement("IFRAME");    
                  i.callbackID 
          = "2";
                  i.style.display
          ="none";
                  i.callback
          =callback;
                  i.src
          ="javascript:\"<script>function visitCountCallBack(o){frameElement.callback(o)}<\/script><script src='"+url+"'><\/script>\""
                  document.body.appendChild(i);
                  i.contentWindow.callback 
          = callback
              }
          }

          function init(){
              
          var spans = document.getElementsByTagName("span");
              
          for(var i=0;i<spans.length;i++){
                  
          var id = spans[i].id;
                  
          var url = "http://g2.qzone.qq.com/fcg-bin/cgi_emotion_list.fcg?uin="+id;
                  
          var callback = function(id){ return function(data){
                      document.getElementById(id).innerHTML 
          = data.visitcount;
                      }
                  }(id);
                  loadjs(url,callback);
              }
          }
          </SCRIPT>
          </HEAD>
          <BODY onload="init()">
          123456 的訪問量:
          <span id="123456"></span><BR>
          2543061 的訪問量:
          <span id="2543061"></span><BR>
          20050606 的訪問量:
          <span id="20050606"></span><BR>
          </BODY>
          </HTML>


          如果需要支持錯誤處理,就稍微麻煩一點了,emu的做法是這樣的: 

           

           

          <HTML>
          <HEAD>
          <SCRIPT LANGUAGE="JavaScript">
          var isIE = !!window.ActiveXObject;
          var useFragment=false;
          function loadjs(url,callback,errcallback){
              
          if(isIE){
                  
          if(useFragment){
                     
          var df = document.createDocumentFragment();    
                      df.visitCountCallBack 
          = function(data){
                          s.onreadystatechange
          =null;
                          df
          =null;
                          callback(data);
                      }
                      
          var s = df.createElement("SCRIPT");
                      df.appendChild(s);
                      s.onreadystatechange
          =function(){
                          
          if(s.readyState=="loaded") {
                              s.onreadystatechange
          =null;
                              df
          =null;
                              errcallback();
                          }
                      }
                      s.src 
          = url;
                  }
          else{
                      
          var i=new ActiveXObject("htmlfile");
                      i.open();
                      i.parentWindow.visitCountCallBack
          =function(i){
                          
          return function(d){
                              i.parentWindow.errcallback
          =null;
                              i
          =null;
                              callback(d);
                          }
                      }(i);
                      i.parentWindow.errcallback
          =function(d){
                          i.parentWindow.errcallback
          =null;
                          i
          =null;
                          errcallback(d);
                      }
                      i.write(
          "<script src=\""+url+"\"><\/script><script defer>setTimeout(\"errcallback()\",0)<\/script>")
                      
          if(i)i.close();//如果數據被cache,運行到這一行的時候有可能回調已經完成,窗口已經關閉。
                  }
              }
          else{
                  
          var i = document.createElement("IFRAME");    
                  i.style.display
          ="none";
                  i.callback
          =function(o){
                      callback(o);
                      i.contentWindow.callback
          =null;
                      i.src
          ="about:blank"
                      i.parentNode.removeChild(i);
                      i 
          = null;
                  };
                  i.errcallback 
          = errcallback;
                  i.src
          ="javascript:\"<script>function visitCountCallBack(data){frameElement.callback(data)};<\/script><script src='"+url+"'><\/script><script>setTimeout('frameElement.errcallback()',0)<\/script>\"";
                  document.body.appendChild(i);
              }
          }

          function init(){
              
          var spans = document.getElementsByTagName("span");
              
          for(var i=0;i<spans.length;i++){
                  
          var id = spans[i].id;
                  
          var url = "http://g2.qzone.qq.com/fcg-bin/cgi_emotion_list.fcg?uin="+id;
                  
          var callback = function(id){ return function(data){
                      document.getElementById(id).innerHTML 
          = data.visitcount;
                      }
                  }(id);
                  
          var errcallback = function(id){ return function(){
                      document.getElementById(id).innerHTML 
          = "無法連接到服務器";
                      }
                  }(id);
                  loadjs(url,callback,errcallback);
              }
          }
          </SCRIPT>
          </HEAD>
          <BODY onload="init()">
          123456 的訪問量:
          <span id="123456"></span><BR>
          2543061 的訪問量:
          <span id="2543061"></span><BR>
          20050606 的訪問量:
          <span id="20050606"></span><BR>
          </BODY>
          </HTML>



          在IE/FIREFOX/OPERA/SAFARI上運行通過。

          這里有幾點說明:IE其實也可以用iframe(試試強行給isIE變量賦false值),不過用iframe的缺點是phantom click(會發出一個頁面跳轉的小聲音)和throbber of doom(應該是指小沙漏型的下載圖標吧?)。

          用document fragment的好處是避免了IE7默認安全模式下面禁止ActiveX的問題。不過利用了IE的一個特點:document fragment不append到document的dom里面的時候,也可以擁有自己的腳本運行空間,可以用script標簽發起請求。這樣用document fragment就可以比iframe使用更少的客戶端資源來完成操作。

          雖然多個版本的IE都支持這個特性,但是emu還是認為其他非IE瀏覽器的處理更為合理,為了防止將來萬一IE fix了這個bug造成措手不及,emu準備了另外兩個備用方案,一個是當useFragment被聲明為false的情況下,可以用一個htmlfile的控件來代替(google在gmail中使用了這個控件,但是造成一些用戶在抱怨IE7下面的安全提示);另一個是如果不能用ActiveX,還可以走非IE瀏覽器的邏輯,用iframe來完成操作,但是耗費的客戶端資源要稍微多一點。用iframe另外兩個的缺點是phantom click(會發出一個頁面跳轉的小聲音)和throbber of doom(應該是指小沙漏型的下載圖標吧?)。針對具體的用戶群的瀏覽器種類,上面幾種方案不用全上,看需要了。

          firefox下面的script標簽其實支持onerror事件(可以寫在標簽里面或者addEventListener上去),其他瀏覽器根據版本的不同對此有不同程度的支持,所以emu決定利用script標簽可以堵塞頁面運行過程的做法,script標簽后面添加延遲的錯誤處理邏輯(在正確的情形下搶先清空掉iframe的內容來取消這個邏輯)。

          posted on 2007-07-10 10:07 emu 閱讀(3127) 評論(11)  編輯  收藏 所屬分類: DHTML和JAVASCRIPT 技術

          評論

          # re: 腳本綁定回調:不可能完成的任務 2007-07-18 22:22 emu
          寫了個頁面測試三種不同的創建document的方式的時間耗費:

          <HTML>
          <HEAD>
          </HEAD>
          <BODY>
          document fragment:<span id="t1"></span><br>
          htmlfile:<span id="t2"></span><br>
          iframe:<span id="t3"></span><br>
          <button onclick="test()">test</button>
          <SCRIPT LANGUAGE="JavaScript">
          var n = 100;
          function test(){
          var t=new Date();
          for(var i=0;i<n;i++)
          document.createDocumentFragment()
          document.getElementById("t1").innerHTML=(new Date()-t);
          if(window.ActiveXObject){
          var t=new Date();
          for(var i=0;i<n;i++)
          new ActiveXObject("htmlfile")
          document.getElementById("t2").innerHTML=(new Date()-t);
          }
          var t=new Date();
          for(var i=0;i<n;i++){
          var tt = document.createElement("IFRAME");
          tt.style.display="none";
          document.body.appendChild(tt);
          }
          document.getElementById("t3").innerHTML=(new Date()-t);
          }
          </SCRIPT>
          </BODY>
          </HTML>

          在ie上基本上是個1:10:100的比例。在firefox上iframe的速度和IE差不多,documentfragment的速度快一些,但是不能創建變量空間,沒有什么意義。safari創建documentfragment更快,可是創建iframe更慢了。最慘的是opera,居然每次插入不可見的iframe都會導致頁面重新渲染,最終瀏覽器都掛掉了。  回復  更多評論
            

          # re: 腳本綁定回調:不可能完成的任務 2008-03-11 19:45 hax
          emu同志,您的這個方法其實還是只是對最終編程者隱藏了callback的名字而已,并沒有提供多少額外的好處也。callback的名字(比如您的visitCountCallback)歸根到底是省不掉的,只是是否暴露給最終編程者的差別。

          因此您說的“事實上只有做到這點,json才能真正想xml一樣變成一個純粹的數據描述方式,擺脫對具體上下文程序的依賴,讓一個數據自由的被不同目的的頁面mashup。比如說,在一個頁面上用json結合腳本技術,把來自不同網站的相同格式的json數據合并顯示到一個頁面上。”其實也站不住腳。或者說,要達到這個目標的前提是,都采用你的這個方案。問題是,如果可以都采用一種方案,那怎么樣都行,不是嗎?

          總之,只是一個callback包裝——如果要包裝的話,不用iframe這樣的技巧,其實也可以辦到。只要用你包裝好的回調,沒有什么是辦不到的。  回復  更多評論
            

          # re: 腳本綁定回調:不可能完成的任務 2008-03-12 01:39 emu
          嗯,我想差別還是蠻大的。
          1 callback的名字如果暴露給最終編程者,首先如果有多個一模一樣的callback(但是其中的數據不同),難免就要相互沖突。
          2 你所說的前提如果是“用callback方式回傳數據”的話,其實這個方式現在是最流行的方式了。
          3 我的方案本來也不依賴iframe。只是不用iframe的話只能在IE上面用而已。  回復  更多評論
            

          # re: 腳本綁定回調:不可能完成的任務 2008-03-13 10:20 hax
          我的意思是,無論你用iframe或是只能用于ie的documentFragment(這明顯是ie的一個問題,憑什么要為任意一個新節點產生一個新的腳本scope?而且我懷疑這會引起內存泄漏——ms的人在說memory leak的patterns那個文章里曾經提到過由這一問題引起的內存泄漏),所付出的代價似乎比不上得到的好處。

          因為不用iframe,你也可以達到向最終編程者隱藏callback名字的目標,只需要callback是由你的框架托管和包裝起來就可以了。差別只在于,這種方式,這個callback名字會污染當前的global上的命名空間,iframe則不會,因為它在一個獨立的scope里——但是付出的代價是有獨立的browserContext(包括window、document等對象)。  回復  更多評論
            

          # re: 腳本綁定回調:不可能完成的任務 2008-03-13 10:36 hax
          因此,iframe只是一個錦上添花的工具。我認為理想的目標是:

          1. callback名字最好是可配置的。因為萬一有些人寫死了callback名字。。。
          2. 有些人不僅寫死了,而且不是callback({...json...}),而是var hardCodeJSON = {...json...},最好也能處理這種情況(肯定是可以做到的)。
          3. 不能每次都搞一個iframe,這樣太浪費。iframe的唯一目的其實是搞一個獨立的scope,所以弄一個iframe就可以了。還有基于前面提到的因素,建議不要用documentFragment。

          那么在一個iframe情況下,你說的“多個一模一樣的callback”的沖突還是存在。這就需要用其他技巧解決。所以反過來,如果其他技巧解決了這個問題,那么iframe的唯一好處就是一丁點都不污染全局命名空間——個人來講,我認為這個好處還是相當有限的。

          下面說一個一般化(也就是不需要iframe)的最土的思路。原本我們插入一個<script>標簽來讀取json。現在我們可以插入3個<script>。第一個做初始化,建立服務器所需要的callback函數,如果有命名沖突,則把之前的那個變量保存起來。第二個還是load JSON。第三個做清理,去掉服務器所用的callback,并把之前的變量恢復起來。

          當然這只是一個一般的描述,實際做的時候,肯定有很多問題要解決,但是從大體上看,應該是行得通的。

            回復  更多評論
            

          # re: 腳本綁定回調:不可能完成的任務 2008-03-13 14:38 emu
          呵呵,假如操作是串行的是行得通的,但是串行的操作本來也很容易處理命名沖突。如果操作是異步化的,并行發起的請求,并且請求的發起順序和相應順序是沒有得到任何保證的,那么你說的“如果有命名沖突,則把之前的那個變量保存起來”能解決得了什么問題呢?
          至于內存泄漏問題,一方面,內存泄漏要靠工具檢查確認是否存在,而不是靠猜它可能存在就簡單的去放棄一個做法的。二方面,就算發現了內存泄漏,應該想辦法定位到內存泄漏的原因并解決它。最后,即使真的一時沒有辦法解決內存泄漏,帶來的影響還是需要評估的?比如有的時候定時器調用函數會造成32byte的內存泄漏,能解決當然很好,不解決會有很大問題嗎?IE一關掉內存就收回來了,用戶又不會一直開著IE三個月不關。  回復  更多評論
            

          # re: 腳本綁定回調:不可能完成的任務 2008-03-13 18:04 我佛山人
          我覺得這種方式不錯,而且我改良過之后,根本不需要建立名為visitCountCallBack的函數,只需要約定var json = {...};

          演示:http://wfsr.net/temp/loadjson.html

          另外我測試發現,firefox不支持相對路徑,safari不支持javascript:這種方法寫入iframe,需要write的  回復  更多評論
            

          # re: 腳本綁定回調:不可能完成的任務 2009-04-24 13:25 boyszz
          簡單事情復雜化的典型表現。  回復  更多評論
            

          # re: 腳本綁定回調:不可能完成的任務[未登錄] 2009-05-20 01:14 小風
          后臺直接吐出一個json變量似乎也可以解決:

          <?php
          echo
          ' var JSON={ ...}; ' ;
          ?>

          然后不是就可以像 jquery 請求返回一個 json 對象一樣么?
          $.post( url, {}, function(json){
          json ....
          } ,'json' );

          好像上面也有和我想的一樣的  回復  更多評論
            

          # re: 腳本綁定回調:不可能完成的任務 2009-05-22 11:39 emu
          IE8beta版本不支持documentFragment擁有獨立的運行空間,正式版又解決了這個問題,因此寫了個小判斷:

          var df = document.createDocumentFragment();
          df.callback=function(){alert("documentFragment有獨立的腳本運行空間")};
          window.callback=function(){alert("不支持documentFragment獨立腳本運行空間")}
          var s = document.createElement("script");
          df.appendChild(s)
          s.src="javascript:'callback()'";
            回復  更多評論
            

          # re: 腳本綁定回調:不可能完成的任務 2010-06-24 11:47 adv
          js + as3也可解決此種問題,缺點是瀏覽器需要播放器支持。
          1。先從as3注冊出一個方法供js調用,作用就是將請求的url轉入到as中,通過as轉發請求。
          3。定義js函數供as調用,來結束請求的返回結果和對應的url地址
          3。擴展as的URLLoader增加一個url屬性來標識發生請求的url。
          4。將結果和對應的url再次傳入js方法。

          如此,可以將并發請求的返回進行對應關系(并非對應順序)。  回復  更多評論
            

          主站蜘蛛池模板: 梁河县| 高邮市| 高尔夫| 邵武市| 宁蒗| 汕尾市| 都兰县| 鄂州市| 皮山县| 邵阳县| 浮山县| 黎川县| 从化市| 荃湾区| 岳西县| 宝丰县| 湖北省| 华容县| 雷波县| 富蕴县| 墨江| 南华县| 洛川县| 司法| 游戏| 南木林县| 宁晋县| 乌什县| 湖口县| 呼玛县| 高雄县| 广宁县| 鄱阳县| 嘉义县| 德兴市| 浦县| 盈江县| 固阳县| 高青县| 施甸县| 托克逊县|