首先感謝某某作者寫的文章:http://www.jb51.net/article/12793.htm
直接上代碼,注意文件名為env.js
原理如下:
一次批量加要加載的文件存入數組,采用Ajax方式異步載入各個文件,然后采用循環方式逐個執行下載下來的Js或者Css文件,如果已經被緩存(localStorage)的則省略下載過程。
由於JS采用的是單線程模式運行,在執行某一個js時會阻塞其它並發的js執行,所以會按順序執行各個js。在執行完所有的腳本之后,圖片會被瀏覽器接着加載,所以第一次加載速度略慢,后面就會比較快了。在JQuery Mobile 1.4.5+FireFox/微信瀏覽器下實測效果不錯,IE就被省略了,我主要是要在微信瀏覽器下使用。
1 //需要引用別的js的時候,就加上如Env.require("cookie.js"),或Env.require("/common/cookie.js"),是用相對路徑還是絕對路徑就看喜好了。 2 //Env.require可用在頁面模板中,也可用在js文件中,但一定要保證執行時env.js被顯式引入。 3 //多次Env.require同一個js(不管用相對還是絕對),只有第一次會加載,所以不會重復。 4 5 //程序最后發行的版本,用於作為緩存鍵的前綴,快速更新緩存 6 var envLastVer = '2014_11_17_17_03'; 7 8 //用於存放通道名稱及通信對象的類,這樣可以通過不同通道名稱來區分不同的通信對象 9 function HttpRequestObject() { 10 this.chunnel = null; 11 this.instance = null; 12 } 13 14 //用於獲取的腳本或css文件保存對象 15 function HttpGetObject() { 16 this.url = null; //要下載的文件路徑 17 this.cache_key = null; //緩存鍵 18 this.chunnel = null; //通道名 19 this.type = null; //類型,js或css 20 this.is_fill = false; //內容是否被填充 21 this.is_exec = false; //內容是否已被執行,防止分幾大塊載入后重復執行 22 } 23 24 //通信處理類,可以靜態引用其中的方法 25 var Request = new function () { 26 27 //通信類的緩存 28 this.httpRequestCache = new Array(); 29 30 //創建新的通信對象 31 this.createInstance = function () { 32 var instance = null; 33 if (window.XMLHttpRequest) { 34 //mozilla 35 instance = new XMLHttpRequest(); 36 //有些版本的Mozilla瀏覽器處理服務器返回的未包含XML mime-type頭部信息的內容時會出錯。 37 //因此,要確保返回的內容包含text/xml信息 38 if (instance.overrideMimeType) { 39 instance.overrideMimeType = "text/xml"; 40 } 41 } 42 else if (window.ActiveXObject) { 43 //IE 44 var MSXML = ['MSXML2.XMLHTTP.5.0', 'Microsoft.XMLHTTP', 'MSXML2.XMLHTTP.4.0', 'MSXML2.XMLHTTP.3.0', 'MSXML2.XMLHTTP']; 45 for (var i = 0; i < MSXML.length; i++) { 46 try { 47 instance = new ActiveXObject(MSXML[i]); 48 break; 49 } 50 catch (e) { 51 } 52 } 53 } 54 return instance; 55 } 56 57 /** 58 * 獲取一個通信對象 59 * 若沒指定通道名稱,則默認通道名為"default" 60 * 若緩存中不存在需要的通信類,則創建一個,同時放入通信類緩存中 61 * @param _chunnel:通道名稱,若不存在此參數,則默認為"default" 62 * @return 一個通信對象,其存放於通信類緩存中 63 */ 64 this.getInstance = function (_chunnel) { 65 var instance = null; 66 var object = null; 67 if (_chunnel == undefined)//沒指定通道名稱 68 { 69 _chunnel = "default"; 70 } 71 var getOne = false; 72 for (var i = 0; i < this.httpRequestCache; i++) { 73 object = HttpRequestObject(this.httpRequestCache[i]); 74 if (object.chunnel == _chunnel) { 75 if (object.instance.readyState == 0 || object.instance.readyState == 4) { 76 instance = object.instance; 77 } 78 getOne = true; 79 break; 80 } 81 } 82 if (!getOne) //對象不在緩存中,則創建 83 { 84 object = new HttpRequestObject(); 85 object.chunnel = _chunnel; 86 object.instance = this.createInstance(); 87 this.httpRequestCache.push(object); 88 instance = object.instance; 89 } 90 return instance; 91 } 92 93 /** 94 * 客戶端向服務端發送請求 95 * @param _url:請求目的 96 * @param _data:要發送的數據 97 * @param _processRequest:用於處理返回結果的函數,其定義可以在別的地方,需要有一個參數,即要處理的通信對象 98 * @param _chunnel:通道名稱,默認為"default" 99 * @param _asynchronous:是否異步處理,默認為true,即異步處理 100 * @param _paraObj:相關的參數對象 101 */ 102 this.send = function (_url, _data, _processRequest, _chunnel, _asynchronous, _paraObj) { 103 if (_url.length == 0 || _url.indexOf("?") == 0) { 104 alert("由於目的為空,請求失敗,請檢查!"); 105 return; 106 } 107 if (_chunnel == undefined || _chunnel == "") { 108 _chunnel = "default"; 109 } 110 if (_asynchronous == undefined) { 111 _asynchronous = true; 112 } 113 var instance = this.getInstance(_chunnel); 114 if (instance == null) { 115 alert("瀏覽器不支持ajax,請檢查!") 116 return; 117 } 118 if (_asynchronous == true && typeof (_processRequest) == "function") { 119 instance.onreadystatechange = function () { 120 if (instance.readyState == 4) // 判斷對象狀態 121 { 122 if (instance.status == 200) // 信息已經成功返回,開始處理信息 123 { 124 _processRequest(instance, _paraObj); 125 } 126 else { 127 alert("您所請求的頁面有異常,請檢查!"); 128 } 129 } 130 } 131 } 132 //_url加一個時刻改變的參數,防止由於被瀏覽器緩存后同樣的請求不向服務器發送請求 133 if (_url.indexOf("?") != -1) { 134 _url += "&requestTime=" + (new Date()).getTime(); 135 } 136 else { 137 _url += "?requestTime=" + (new Date()).getTime(); 138 } 139 if (_data.length == 0) { 140 instance.open("GET", _url, _asynchronous); 141 instance.send(null); 142 } 143 else { 144 instance.open("POST", _url, _asynchronous); 145 instance.setRequestHeader("Content-Length", _data.length); 146 instance.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); 147 instance.send(_data); 148 } 149 if (_asynchronous == false && typeof (_processRequest) == "function") { 150 _processRequest(instance, _paraObj); 151 } 152 } 153 } 154 155 var Env = new function () { 156 this.needLoadObject = new Array(); 157 158 //獲取env.js文件所在路徑 159 this.envPath = null; 160 this.getPath = function () { 161 this.envPath = document.location.pathname; 162 this.envPath = this.envPath.substring(0, this.envPath.lastIndexOf("/") + 1); 163 var _scripts = document.getElementsByTagName("script"); 164 var _envPath = null; 165 var _scriptSrc = null; 166 for (var i = 0; i < _scripts.length; i++) { 167 _scriptSrc = _scripts[i].getAttribute("src"); 168 if (_scriptSrc && _scriptSrc.indexOf("env.js") != -1) { 169 break; 170 } 171 } 172 if (_scriptSrc != null) { 173 if (_scriptSrc.charAt(0) == '/') { 174 this.envPath = _scriptSrc.substr(0, _scriptSrc.length - 6); 175 } 176 else { 177 this.envPath = this.envPath + _scriptSrc.substr(0, _scriptSrc.length - 6); 178 } 179 } 180 } 181 this.getPath(); 182 183 //獲取文件后綴名 184 this.getFileExt = function (fileUrl) { 185 var d = /\.[^\.]+$/.exec(fileUrl); 186 return d.toString().toLowerCase(); 187 } 188 189 //依次放入要載入的文件 190 this.pushNeedLoad = function (url) { 191 var _absUrl = null; 192 if (url.charAt(0) == '/') 193 _absUrl = url; 194 else 195 _absUrl = this.envPath + url; 196 197 var object = new HttpGetObject(); 198 object.url = _absUrl; 199 object.cache_key = envLastVer + _absUrl; //利用版本號+絕對路徑生成緩存鍵 200 object.chunnel = 'ch' + (this.needLoadObject.length + 1); 201 object.type = this.getFileExt(_absUrl); 202 203 //嘗試從緩存獲取 204 var cacheContent = localStorage.getItem(object.cache_key); 205 if (cacheContent) { object.is_fill = true; } 206 207 this.needLoadObject.push(object); 208 return this; 209 } 210 211 //依次裝載要處理的文件 212 this.batchLoad = function () { 213 for (var i = 0; i < this.needLoadObject.length; i++) { 214 var item = this.needLoadObject[i]; 215 var processGet = function (_instance, _paraObj) { 216 localStorage.setItem(_paraObj.cache_key, _instance.responseText); //緩存文件 217 _paraObj.is_fill = true; 218 } 219 if (item.is_fill == false) { 220 Request.send(item.url, "", processGet, item.chunnel, false, item); //采用同步方式載入 221 } 222 } 223 return this; 224 } 225 226 //依次執行要處理的文件 227 this.batchExec = function () { 228 var runCss = function (_css) { document.write('<style type="text/css">' + _css + '</style>'); } 229 var runJs = function (_js) { 230 if (window.execScript) 231 window.execScript(_js); 232 else 233 window.eval(_js); 234 } 235 //依次執行,由於js為單線程執行,每執行一個js都會阻塞其它,所以可以保證順序執行 236 for (var i = 0; i < this.needLoadObject.length; i++) { 237 var item = this.needLoadObject[i]; 238 if (item.is_exec == false) { 239 if (item.type == '.js') { 240 runJs(localStorage.getItem(item.cache_key)); 241 item.is_exec = true; //標記已執行,下次不會再執行 242 } 243 else if (item.type == '.css') { 244 runCss(localStorage.getItem(item.cache_key)); 245 item.is_exec = true; //標記已執行,下次不會再執行 246 } 247 } 248 } 249 } 250 }
下面是調用方法:
1 Env.pushNeedLoad("jquery.mobile-1.4.5/jquery.min.js") 2 .pushNeedLoad("jquery.mobile-1.4.5/jquery.mobile-1.4.5.min.css") 3 .pushNeedLoad("/plus_in/weixin/procedure/scripts/task.util.js") 4 .pushNeedLoad("/plus_in/weixin/procedure/scripts/emcp.mobile.js") 5 .pushNeedLoad("/plus_in/weixin/procedure/scripts/common.index.js") 6 .pushNeedLoad("jquery.mobile-1.4.5/jquery.mobile-1.4.5.min.js") 7 .pushNeedLoad("mobiscroll.2.6/css/mobiscroll.custom-2.6.2.min.css") 8 .pushNeedLoad("mobiscroll.2.6/js/mobiscroll.custom-2.6.2.min.js") 9 .pushNeedLoad("/plus_in/weixin/procedure/style/base.css") 10 .batchLoad().batchExec();
通過火狐F12觀察,發現上面的腳本第一次會被加載,后面將會直接從localstorage中讀取,節省了很多,將js用於微信瀏覽器后,也節省了很多帶寬。不過第一次加載還是有些慢的,畢竟還是有那么多數據。