前一段時間分析過require.js原始碼,整體的分析有些泛泛而,談內容有些空洞,沒有把握住requirejs依賴處理的精髓, 這是分析require.js源碼最失敗的地方。
雖然沒有真正的理解其實作細節,但是對其原始碼組織以及基本的邏輯執行有了整體的了解。
本文是參考網路上的源碼,分析其思想實現的簡易的模組載入器,旨在加深對於require.js的認知與理解。
首先明確的幾點如下:
每呼叫一次require函數,就會建立一個Context物件
Module物件表示模組對象,基本的操作都是該物件的原型方法
上面兩個物件是實作該簡易模組載入器核心,載入過程中的處理流程如下圖所示:
#Module物件的屬性有:
mid:表示模組id
src:模組路徑
name: 模組名稱
deps:模組的依賴清單
callback:回呼函數
errback:錯誤處理函數
status:模組狀態
exports:與回呼函數參數序列對應的模組輸出
Module的原型物件有以下幾個方法:
init:Module物件的初始化處理
fetch:建立script節點並追加到元素節點上
checkCycle:處理循環依賴,傳回目前的循環依賴清單
handleDeps:處理依賴清單
changeStatus:改變模組的狀態,主要處理模組是否載入成功
execute:依賴的模組都載入成功後,參數清單的取得
事實上,處理依賴清單是define以及require中處理,define函數以及require函數的處理程式碼如下:
首先看require函數,實例:
require(['a', 'b'], function(a, b) { console.log(a, b); });
從上面可以看出,呼叫require函數,依賴列表是['a', 'b'],回呼函數是function(a, b) {console.log(a, b);}
從require程式碼可以就看出,呼叫Context建構函數建立一個Context對象,現在看下Context建構函數的處理:let Context = function(deps, callback, errback) {
this.cid = ++contextId; this.init(deps, callback, errback); } ; Context.prototype.init = function(deps, callback, errback) { this.deps = deps; this.callback = callback; this.errback = errback; contexts[this.cid] = this; } ;
上面中重要的是contexts[this.cid] = this;將目前的Context物件註冊到全域contexts物件集合中。
然後呼叫handleDeps函數,該函數處理依賴列表,具體的程式碼如下:handleDeps: function() {
let depCount = this.deps ? this.deps.length : 0; // require.js中处理循环依赖的处理 let requireInDep = (this.deps || []).indexOf('require'); if (requireInDep !== -1) { depCount--; this.requireInDep = requireInDep; this.deps.splice(requireInDep, 1); } // 处理循环依赖情况 let cycleArray = this.checkCycle(); if (cycleArray) { depCount = depCount - cycleArray.length; } // depCount表示当前模块的依赖模块数,depCount为0表示模块中某一依赖加载完成 this.depCount = depCount; if (depCount === 0) { this.execute(); return; } // 遍历依赖列表,创建Module对象,并且将当前模块与其依赖的关系构建出来maps this.deps.forEach((depModuleName) => { if (!modules[depModuleName]) { let module = new Module(depModuleName); modules[module.name] = module; } if (!maps[depModuleName]) { maps[depModuleName] = []; } maps[depModuleName].push(this); } );
}循環依賴的處理
本次實作程式碼中的循環依賴的處理,是require.js中官方的方法,就是傳遞require在回呼函數中再次require,為什麼這樣就可以解決循環依賴?
就本次實作而言,因為require一次就會建立一個Context物件。主要程式碼如下:// require.js中處理循環依賴的處理let requireInDep = (this.deps || []).indexOf('require');
if (requireInDep !== -1) { depCount--; this.requireInDep = requireInDep; this.deps.splice(requireInDep, 1); } // 获取循环依赖 let cycleArray = this.checkCycle(); if (cycleArray) { depCount = depCount - cycleArray.length; } // execute函数中代码// 插入require到回调函数的参数列表中if (this.requireInDep !== -1 && this.requireInDep !== undefined) { arg.splice(this.requireInDep, 0, require); }
結束語
紙上得來終覺淺,覺知此事要躬行,通過實現簡易的模組加載器,對於require.js模組加載的思想以及邏輯處理更加地清晰。
雖然與require.js對於js檔案的非同步載入的處理方式不同,但本質是一樣的,require.js是添加script節點到head標籤中,並且script添加async屬性來實現異步加載的。
#以上是用js實作簡易模組載入器的方法的詳細內容。更多資訊請關注PHP中文網其他相關文章!