借助HTML5 Web Worker 解決資源預加載 的一些頑疾.


有時.為了訪問下一個頁面時,可以更快.我們可能在當前頁面 onload之后,利用空閑 去預加載下一個頁面,以及相關資源.
為了不影響當前頁面.我們必須保證.這些預加載的html css js 資源不被解析或執行.
 
先看看一些其他可選擇的方式: 
 new Image().src 方式: 缺點 只能完整的預加載 mimeType 是 image/* 的資源. 其他資源只會被加載一個包的數據.就會被終止下載.
 
 link rel="stylesheet"  缺點 ie css表達式 css樣式將被渲染. 非 ie 無法獲知onload onerror
 link rel="stylesheet" disabled="disabled" ...不渲染css 預加載. 但FF 可能無效.除非額外配置其他屬性 .
 link rel="stylesheet" media="cache etc..."   不渲染css  缺點  非ie 無onload onerror ,ie 會觸發一次 draw 渲染頁面
 
 link rel="alternate stylesheet"   不渲染css  缺點  非ie 無onload onerror ,ie 會觸發一次 draw 渲染頁面
 link rel="prefetch"  缺點 gecko 瀏覽器支持 ff1.5+ .  動態添加 link節點無效.  優點自動在window.onload以后才去加載資源. 
 其他如 font-face  background-image 都有無法onload onerror . 不停添加CSS Rule  無法及時回收資源的問題
document.createElement('object').data = url 方式. 缺點 :FF 加載 html 時導致解析html 並執行html中腳本資源
object.type ="application/x-shockwave-flash" 方式 . 缺點:一但資源為flash ....

 

yahoo的加載.js .css 且不解析不渲染的解決方法如下:
<script  type="text/cache"  src="url"></script> (text/這里是隨意的.我們是要故意讓瀏覽器不認識.)
如此瀏覽器可以預先加載任何資源 而不會對資源進行解析和執行.比如某個.js. 
但是 Firefox 並不支持這樣做.  如果Firefox不認識type.那么就不會發起一個http請求.
yahoo的辦法是用object Element 來加載 ,Firefox3.0+的瀏覽器允許你這么做. 但是如果我們需要加載一個text/html資源.那么object標簽加載它會導致瀏覽器解析這個html,並執行里面的腳本. 這個是我們不能容忍的.  想象一下.我們當前頁面跑的好好的. 突然因為預加載某個頁面 而導致播放音樂.或執行某頁的腳本.  那么對於Firefox 我們需要借助 Link Element (rel = "stylesheet")來做. 它可以保證加載 HTML不解析. 但是Link Element 在Firefox中.沒有onload ,onerror . 一但我們需要在Firefox解析加載完當前資源后.我們是需要自動把 動態添加的 Link 標簽從DOM上移除. script標簽也是如此.
而chrome8+,Safari5+開始就不再支持 type不認識的script 資源加載了.
Opera瀏覽器對於script 標簽無能為力的地方同Firefox 的Link Element 一樣.一單不認識type .那么onload onerror都無效.
為了代碼更健壯. 我想到了使用 HTML5 Web Worker 的 importScripts()方法. 
不熟悉該方法的朋友可以到   http://www.cnblogs.com/_franky/archive/2010/11/23/1885773.html 
顯然 Worker 為我們提供了一個相對完美的沙箱機制.   (並不是完全完美.原因最后說.)
可惜的是.Worker+script方案 仍然無法解決 Opera9.6- Firefox3.0- 的瀏覽器.
如果你需要解決這兩個瀏覽器.的早期版本.那么需要借助一些反面教材的經驗.  對於opera的早期版本.你可需要借助輪詢來解決.
因為 雖然opera 對於不認識 type的script標簽. onloda onerror都失效. 但是.實際上. scriptElement.readyState 是變化的.
我們可以自己輪詢 來查看其 readyState的值.(就好像ie那樣做).  來判斷資源是否加載完畢. 
當然.我最希望聽到的就是.我們的項目部需要考慮 opera 9.6 FF3.0 以及之前的那些老古董們..
好.最后給出兩組偽代碼. 當然其實只是代碼依托的一些基礎對象不存在,而導致無法執行: 
 
//反面教材. 
//script Element + link Element(FireFox加載HTML) + object ELement (FireFox  JS CSS ...)
//需要在恰當時間 調用dispose()方法用來 移除過多的節點.(受Firefox 和 Opera拖累)
void function (win,doc,head,ns){
     var _class = '__PRELOAD_CLEARTEMPELEMENTS__' , _tag = 'script';
     if(ns.preLoad){
          return;
     }
     var _preLoad = ns.singlePreLoad = !!doc.getBoxObjectFor || win.mozInnerScreenX != null ? (_tag = '*') && function(sURL,isHTML){
               //FF3.0+開始支持 object加載其他數據類型. 如果是html則會解析該html
               var el;
               if(isHTML){
                    el = doc.createElement('link');
                    el.type = 'text/css';
                    el.rel = 'stylesheet';
                    el.href = sURL;
               }else{
                    el = doc.createElement('object');
                    el.data = sURL;
               }
               el.className = _class;
               head.appendChild(el);
          }: function(sURL){//!FF
                    var el = doc.createElement('script');
                    el.type = 'text/C';
                    el.className = _class;
                    el.src = sURL;
                    head.appendChild(el);          
          };
     ns.preLoad = function(aURL){
          typeof aURL == 'string' && (aURL = [aURL]);
          for(var i = 0 , l = aURL.length ; i < l ; i++)_preLoad(aURL[i]);
     };
     ns.preLoad.dispose = function (){
          var list = ns.DOM.$C('__PRELOAD_CLEARTEMPELEMENTS__',_tag,head);
          for (var i = list.length ; i-- ;){
               head.removeChild(list[i]);
          }
          list = null;
     };
          
}(window,document,document.getElementsByTagName('head')[0],Visit.Util);

 

//worker + script  方案
引入worker.js
onmessage = function(e){
    var a = e.data.toString().split(',');
          for(var i = 0 , l = a.length ;i++){
               try{ 
                    //避免因加載失敗導致腳本不在繼續執行下去. 否則 一個imirtScripts.apply(null,a); 就解決問題了.
                    //現在,還導致失去了並行加載的優勢.但是也是在是木有辦法.
                    importScripts(a[i]);
               }catch(e){}
          }
    postMessage('Loaded');
};



void function (win,doc,head,ns){
     var _dom = ns.DOM,
          _workerURL = 'js/worker.js',//worker.js Path
          _emptyFn = function(){};
     
     if(ns.preLoad){
          return;
     }
     if(win.Worker){//FF3.5+ Opera10+  Safari4+ Chrome3+ (需要 worker.js支持)
          ns.preLoad = function(aURL){
               var _w = new win.Worker(_workerURL);
               _w.onerror = _emptyFn;
               _w.onmessage = function(e){
                    e.data == 'VISITLOADED' && (_w.terminate(),1) && (_w = null);//
               };
               _w.postMessage(aURL);
          };
     }else if(!doc.getBoxObjectFor || !win.opera){//IE6+ Safari3- Chrome2-
          ns.preLoad = function(aURL){
               for(var i = 0,l = aURL.length ; i < l ; i++){
                    _dom.loadJS(aURL[i],null,null,'text/C');
               }
          };
          
     }else{//FF3.0-  Opera9.6-
          ns.preLoad = _emptyFn;
     }
          
}(window,document,document.getElementsByTagName('head')[0],Visit.Util);

 

最后說說為什么說Woker 的沙箱不夠好.

 

1.importScripts方法加載的資源如果是個腳本.那么實際上它是會執行的. 所以一旦資源擁有者了解這一切,那么可以給你一個執行 postMessage('loaded')語句的腳本.

顯然主頁面  : e.data == 'loaded' && (_w.terminate(),1) && (_w = null); 這里就會被執行.那么結果就不准確了. 雖然此漏洞可以通過 一個隨機key驗證方式繞過. 但考慮到 2 和 3 的問題. 

僅僅修復。此漏洞 意義並不大. 而且 importScripts 的語句還要放到一個獨立的閉包中..額外損耗. 還沒意義.得不償失.

 

2. 加載的資源執行死循環的腳本.那么 除了FF 其他瀏覽器的 postMessage('loaded') 可能就無法被執行到.

 

3. 另外應該注意.opera 瀏覽器 worker 內部調用 self.close()后 帶來的問題...這個問題我們無法繞過去..很糾結. 不過好在 僅僅是使我們無法確定資源是否加載完畢而已...

損失可以忽略. 只要沒有安全問題.還是可以接受的.如果可能的話 加入一個 超時機制 來彌補這種問題所帶來的風險應該是ok的...畢竟我們最終的目的是為了預加載資源.

 

 

4. 一但importScripts 引入的腳本 執行類似的腳本 while(!window) postMessage('hellow'); 那么就是個悲劇 主頁面進程相當的卡. 

類似的還有 importScripts()遞歸調用.

 

防堵這兩個問題也是有辦法的. 大家可以自己想想看(提示黑名單制度+超時制度.)

參考偽代碼 :

 _w.onmessage = function(e){
    win.clearTimeout(_timer);
    _w.terminate();
    _w = _w.onmessage = null;
    e.data != key && _preLoad.addBlackList && _preLoad.addBlackList((aURL.shift(),aURL),e.data);
};

 

 


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com