今天,我們來聊聊 node 的模組,主要內容分別有:
1.什麼是模組化?模組化都有哪些規範?
2.node 模組匯入具體是如何實現的?
模組化歷史
單例模式
-
保證一個類僅有一個例項,並提供一個訪問它的全域性訪問點。
-
實現單例核心思想 用一個變數來標誌當前是否已經為某個類建立過物件,如果是,則在下一次獲取該類的例項時,直接返回之前建立的物件。
-
單例模式抽象,分離建立物件的函式和判斷物件是否已經建立
var getSingle = function (fn) {
var result;
return function () {
return result || ( result = fn.apply(this, arguments) );
}
};
複製程式碼
形參 fn 是我們的建構函式,我們只要傳入任何自己需要的建構函式,就能生成一個新的惰性單例。
閉包
- 函式執行後返回一個引用空間這個空間被外部引用,此空間無法銷燬。這就叫閉包 函式執行的時候也會產生一個閉包
AMD
- 非同步模組定義
- 並非JavaScript原生支援,使用AMD規範進行頁面開發需要用到對應的庫函式,也就是大名鼎鼎RequireJS,實際上AMD 是 RequireJS 在推廣過程中對模組定義的規範化的產出。
requireJS主要解決兩個問題
- 1、多個js檔案可能有依賴關係,被依賴的檔案需要早於依賴它的檔案載入到瀏覽器。
- 2、js載入的時候瀏覽器會停止頁面渲染,載入檔案越多,頁面失去響應時間越長。
CMD
- 通用模組定義
- CMD有個瀏覽器的實現SeaJS,SeaJS要解決的問題和requireJS一樣,只不過在模組定義方式和模組載入(可以說執行、解析)時機上有所不同。
AMD和CMD區別:
- 1、AMD推崇依賴前置,在定義模組的時候就要宣告其依賴的模組。
- 2、CMD推崇就近依賴,只有在用到某個模組的時候再去require。
Commonjs
- 1.模組實現(一個 js 檔案就是一個模組)為了實現模組化的功能每個檔案外面都包含一個閉包。
- 2.規定如何匯出一個模組 module.exports。
- 3.如果匯入一個模組 require。
node 模組匯入實現步驟
node 匯入模組的方式:
const fs = require("fs");
複製程式碼
我們通過這個入口,一步步的看下 node 是如何實現模組的匯入的,斷點除錯,走起!
首先我們看到,在 node 核心檔案 module.js 中,定義了一個 Module 類,並且在類的原型上定義了一個 require 方法,而這個方法就是在給定的檔案路徑下載入模組,並且返回該模組的"exports"物件。
而 require 呼叫了 Module 類上的靜態方法 _load,那我們進去看看這個 _load 方法是如何實現的吧:
很明顯,這裡就node module匯入的核心程式碼,那麼這裡都做了些什麼事情呢,我們一一來分析:
-
1.如果快取中已經存在了將要匯入的模組,則直接返回其"exports"物件。
-
2.如果這個模組是原生的模組,那麼則呼叫NativeModule.require()來返回對應的結果。
-
3.如果以上2種都沒有,則執行以下操作:
-
1.根據檔名,解析出檔案的絕對路徑,其對應的是這個操作:
-
2.如果快取中已經有這個模組,則直接從快取中獲取並返回"exports"物件:
-
3.如果快取中沒有,則檔案的絕對路徑建立一個模組,並且將這個模組放入到快取中:
-
4.最後讀取檔案模組中的內容,將內容放置在"exports"物件上並最終返回"exports"物件:
-
步驟小結:
通過上面的分析我們不難發現:
- Moudle 是一個類。那我們來看看,node裡面的Module類都有哪些屬性呢?
- 要實現一個 Module._load 方法實現模組的載入。
- Module._resolveFilename 方法是用來解析檔案,獲取檔案的絕對路徑。
- Module._cache 模組快取
- 建立一個模組,放到快取中
- Module._extensions 不同檔案型別解析方式不同,這點我們在實現中細細講解
如何實現一個自己的模組匯入?
根據上面的邏輯和分析,我們簡單的實現下模組的匯入:
首先,我們要讀取檔案並解析js檔案,所以需要使用node底層一些方法:
const fs = require("fs");
const path = require("path");
const vm = require("vm");
// 定義Module類
function Module(file) {
this.id = file;//當前模組標識
this.exports = {};//模組必有屬性,模組匯出時屬性掛載在該物件上
}
複製程式碼
接下來的第二步,
Module類中的靜態方法Module._load:
function req(moduleId) {
let p = Module._resolveFileName(moduleId);
// 判斷快取中是否已經存在該模組,如果存在則直接返回模組的"exports"物件
if (Module._cacheModule[p]) {
return Module._cacheModule[p].exports;
}
// 快取中沒有載入過這個模組,則構建一個模組
let module = new Module(p);
// 載入模組
let content = module.load(p);
// 將建立出來的模組放入到快取中,下次呼叫時直接從快取中獲取
Module._cacheModule[p] = module;
module.exports = content;
// 最後返回模組的exports物件
return module.exports;
}
複製程式碼
第三步:
我們需要解析檔案的具體路徑,讓我們一起看看 Module._resolveFileName 方法的實現吧:
// 解析絕對路徑的方法,返回一個絕對路徑
Module._resolveFileName = function (moduleId) {
let resolvePath = path.resolve(moduleId);
// 判斷檔案是否有字尾,如果沒有則加上字尾
if (!path.extname(moduleId)) {
// 獲取物件所有的key,返回一個物件所有key的集合
let arr = Object.keys(Module._extensions);
for (let i = 0; i < arr.length; i++) {
// 沒有字尾將組成完整的檔案路徑
let file = resolvePath + arr[i];
try {
// 判斷檔案是否存在並且能夠被訪問
fs.accessSync(file);
return file;
} catch (error) {
console.log(error);
}
}
} else {
return resolvePath;
}
};
複製程式碼
第四步:
在拿到檔案的絕對路徑之後呢,我們將檢查模組快取中是否已經載入過這個模組,因此我們在類上定義了一個模組快取物件:
// 模組快取物件,是以模組的絕對路徑作為key來進行快取
Module._cacheModule = {};
複製程式碼
node模組匯入時如何解析要匯入的檔案
如果快取中沒有要載入的模組物件,則構建一個模組,並讀取模組的內容,在這裡要注意的是:
- 不同檔案模組載入和讀取方式是不一樣的
如上圖所示,對於json型別的檔案,我們需要將檔案的內容讀取出來並解析成JSON物件並掛載在module.exports物件身上即可
第五步:
而對於js檔案型別來說,有著獨特的解析方式,所以我們根據檔案的字尾來使用不同的載入方式:
// 根據不同檔案型別載入模組
Module.prototype.load = function (filePath) {
// 獲取檔案字尾
let ext = path.extname(filePath);
// 根據檔案字尾呼叫不同的檔案模組載入策略,讀取檔案的內容
let content = Module._extensions[ext](this);
return content;
}
複製程式碼
而 Module._extensions 這個物件中則存放著真正解析檔案的具體方法:
// js模組包裹陣列
Module._wrapper = ["(function(exports,require,module){", "})"];
// 檔案模組載入策略物件,包括js檔案和json檔案
Module._extensions = {
".js": function (module) {
// 讀取js檔案中的js
let scripts = fs.readFileSync(module.id, "utf8");
let fn = Module._wrapper[0] + scripts + Module._wrapper[1];
// 在沙箱中執行js程式碼,將不受上下文環境的影響
vm.runInThisContext(fn).call(module.exports,module.exports,req,module);
return module.exports;
},
".json": function (module) {
// json檔案為讀取內容後進行解析
return JSON.parse(fs.readFileSync(module.id, "utf8"));
}
}
複製程式碼
最後,我們通過一張gif圖來瀏覽下所有程式碼,並跑下最終的結果:
好了,關於node 模組匯入就先提到這裡,歡迎大家多多提問題,謝謝!