衆所周知,模塊化開是會將複雜的系統分解成高內聚、低耦合的模塊,使系統開發變得可控、可維護、可拓展,提升模塊複用率。而在js中,異步模塊模式的狀況則比較多,所謂異步模塊模式,是在請求發出後,繼續其餘業務邏輯,直到模塊加載完成後執行後續的邏輯,實現模塊開發中對模塊加載完成後的引用。git
今天就來分析一下異步加載模塊,本文經過建立與調度模塊、加載模塊和設置模塊三個方面來分析github
建立與調度方法集模塊建立方法於一身。在這個方法中腰遍歷全部依賴模塊,並判斷全部模塊都存在纔可執行回調函數,不然加載相應文件,直到文件加載完成才執行回調函數。ajax
實現代碼以下:數組
/** * 建立或調用模塊方法 * @param url 模塊url * @param modDeps 依賴模塊 * @param modCallback 模塊主函數 */ F.module = function(url, modDeps, modCallback) { // 將參數轉化爲數組 let args = [].slice.call(arguments); // 獲取模塊構造函數(參數數組中最後一個參數成員) let callback = args.pop(); // 獲取依賴模塊(緊鄰回調函數參數,且數據類型爲數組) let deps = (args.length && args[args.length - 1] instanceof Array) ? args.pop() : []; // 該模塊url(模塊ID) url = args.length ? args.pop() : null; // 依賴模塊序列 let params = []; // 未加載的依賴模塊數量統計 let depsCount = 0; // 依賴模塊序列中索引值 let i = 0; // 依賴模塊序列長度 let len; if(len = deps.length) { while(i < len) { (function(i) { // 增長未加載依賴模塊數量統計 depsCount++; // 異步加載依賴模塊 loadModule(deps[i], function(mod) { // 依賴模塊序列中添加依賴模塊數量統一減一 depsCount--; params[i] = mod; // 若是依賴模塊所有加載 if(depsCount === 0) { // 在模塊緩存器中矯正該模塊,並執行構造函數 setModule(url, params, callback); } }); })(i); // 遍歷下一個模塊 i++; } // 無依賴模塊,直接執行回調函數 } else { // 在模塊緩存器中矯正該模塊,並執行構造函數 setModule(url, [], callback); } }
在module方法中有兩個方法尚未定義:loadModule(加載模塊)和setModule(設置模塊),接下來,便一一介紹緩存
loadModule方法目的是加載依賴模塊對應的文件並執行回調函數。對此可分三種狀況處理:app
代碼以下:異步
// 模塊緩存器。存儲已建立模塊 let moduleCache = {}; /** * 異步加載依賴模塊所在文件 * @param moduleName 模塊路徑(id) * @param callback 模塊加載完成回調函數 */ function loadModule(moduleName, callback) { let _module; // 若是依賴模塊被要求加載過 if(moduleCache[moduleName]) { _module = moduleCache[moduleName]; // 若是模塊加載完成 if(_module.status === 'loaded') { // 執行模塊加載完成後回調函數 setTimeout(callback(_module.exports), 0); } else { // 緩存該模塊所處文件加載完成回調函數 _module.onload.push(callback); } // 模塊第一次被依賴引用 } else { // 緩存該模塊初始化信息 moduleCache[moduleName] = { // 模塊ID moduleName: moduleName, // 模塊對應文件加載狀態(默認加載中) status: 'loading', // 模塊接口 exports: null, // 模塊對應文件加載完成回調函數緩衝器 onload: [callback] }; // 加載模塊對應文件 loadScript(getUrl(moduleName), moduleName); } }
loadModule方法中在加載模塊對應文件時須要引用loadScript加載腳本方法和getUrl獲取文件路徑方法。這兩個方法等實現以下:async
function getUrl(moduleName) { // 拼接完整的文件路徑字符串,如'lib/ajax' => 'lib/ajax.js' return String(moduleName).replace(/\.js$/g, '') + '.js'; } function loadScript(src, id) { let _script = document.createElement('script'); // 文件類型 _script.type = 'text/JavaScript'; // 確認編碼 _script.charset = 'UTF-8'; // 異步加載 _script.async = true; // 文件路徑 _script.src = src; // 文件id _script.id = id; document.getElementsByTagName('head')[0].appendChild(_script); }
由getUrl方法能夠看出,模塊的id和文件的路徑應該是一一對應的關係,否則在解析路徑時就會出錯。不過,也能夠將getUrl改形成你喜歡的格式,在這裏就很少說了。模塊化
表面上看設置模塊就是執行回調函數的,但實質上它作了三件事:函數
設置模塊代碼以下:
/** * 設置模塊並執行模塊構造函數 * @param moduleName 模塊ID名稱 * @param params 依賴模塊 * @param callback 模塊構造函數 */ function setModule(moduleName, params, callback) { let fn; // 若是模塊被調用過 if(moduleCache[moduleName]) { let _module = moduleCache[moduleName]; // 設置模塊已經加載完成 _module.status = 'loaded'; // 矯正模塊接口 _module.exports = callback ? callback.apply(_module, params) : null; // 執行模塊文件加載完成回調函數 while(fn = _module.onload.shift()) { fn(_module.exports); } } else { // 模塊不存在(匿名模塊),則直接執行構造函數 callback && callback.apply(null, params); } // 刪除加載的script標籤 deleteScript(moduleName); }
在設置代碼執行後,便將加載到head中的script標籤刪除,純屬我的代碼潔癖,能夠沒有的,刪除標籤代碼以下:
function deleteScript(id) { const deleteJs = document.getElementById(id); console.log(deleteJs); if(deleteJs) { document.getElementsByTagName('head')[0].removeChild(deleteJs); } }
異步模塊模式不只減小了多人開發過程當中變量、方法名被覆蓋的問題,並且增長了模塊依賴,使開發者沒必要擔憂某些方法還沒有加載或未加載完成形成的沒法使用問題。異步加載部分功能也能夠將更多首屏沒必要要的功能剝離出去,減小首屏加載成本。
demo能夠在此看到: