借助HTML5 Web Worker 解决资源预加载 的一些顽疾.
//反面教材. //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);
};