您好,登錄后才能下訂單哦!
本篇文章給大家分享的是有關Nodejs中的模塊系統該如何使用,小編覺得挺實用的,因此分享給大家學習,希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。
早期 JavaScript 是為了實現簡單的頁面交互邏輯, 但隨著時代發展, 瀏覽器不單單僅只能呈現簡單交互, 各種各樣的網站開始大放光彩。隨著網站開始變得復雜化,前端代碼日漸增多,相對比起其他靜態語言,JavaScript 缺少模塊化的弊端開始暴露出來,如命名沖突。因此為了方便前端代碼的維護和管理,社區開始了對模塊化規范的定義。在這過程中,出現了很多的模塊化規范,如CommonJS
, AMD
, CMD
, ES modules
,本文章主要講解Node
中根據CommonJS
實現的模塊化。
首先,在 Node 世界里,模塊系統是遵守CommonJS
規范的,CommonJS
規范里定義,簡單講就是:
每一個文件就是一個模塊
通過module
對象來代表一個模塊的信息
通過exports
用來導出模塊對外暴露的信息
通過require
來引用一個模塊
核心模塊: 如 fs,http,path 等模塊, 這些模塊不需要安裝, 在運行時已經加載在內存中。【推薦學習:《nodejs 教程》】
第三方模塊: 通過安裝存放在 node_modules 中。
自定義模塊: 主要是指 file 模塊,通過絕對路徑或者相對路徑進行引入。
我們上面說過,一個文件就是一個模塊,且通過一個 module 對象來描述當前模塊信息,一個 module 對象對應有以下屬性: - id: 當前模塊的id - path: 當前模塊對應的路徑 - exports: 當前模塊對外暴露的變量 - parent: 也是一個module對象,表示當前模塊的父模塊,即調用當前模塊的模塊 - filename: 當前模塊的文件名(絕對路徑), 可用于在模塊引入時將加載的模塊加入到全局模塊緩存中, 后續引入直接從緩存里進行取值 - loaded: 表示當前模塊是否加載完畢 - children: 是一個數組,存放著當前模塊調用的模塊 - paths: 是一個數組,記錄著從當前模塊開始查找node_modules目錄,遞歸向上查找到根目錄下的node_modules目錄下
說完CommonJS
規范,我們先講下module.exports
與exports
的區別。
首先,我們用個新模塊進行一個簡單驗證
console.log(module.exports === exports); // true
可以發現,module.exports
和epxorts
實際上就是指向同一個引用變量。
demo1
// a模塊 module.exports.text = 'xxx'; exports.value = 2; // b模塊代碼 let a = require('./a'); console.log(a); // {text: 'xxx', value: 2}
從而也就驗證了上面demo1
中,為什么通過module.exports
和exports
添加屬性,在模塊引入時兩者都存在, 因為兩者最終都是往同一個引用變量上面進行屬性的添加.根據該 demo, 可以得出結論: module.exports
和exports
指向同一個引用變量
demo2
// a模塊 module.exports = { text: 'xxx' } exports.value = 2; // b模塊代碼 let a = require('./a'); console.log(a); // {text: 'xxx'}
上面的 demo 例子中,對module.exports
進行了重新賦值, exports
進行了屬性的新增, 但是在引入模塊后最終導出的是module.exports
定義的值, 可以得出結論: noed 的模塊最終導出的是module.exports
, 而exports
僅僅是對 module.exports 的引用, 類似于以下代碼:
exports = module.exports = {}; (function (exports, module) { // a模塊里面的代碼 module.exports = { text: 'xxx' } exports.value = 2; console.log(module.exports === exports); // false })(exports, module)
由于在函數執行中, exports 僅是對原module.exports
對應變量的一個引用,當對module.exports
進行賦值時,exports
對應的變量和最新的module.exports
并不是同一個變量
require
引入模塊的過程主要分為以下幾步:
解析文件路徑成絕對路徑
查看當前需要加載的模塊是否已經有緩存, 如果有緩存, 則直接使用緩存的即可
查看是否是 node 自帶模塊, 如 http,fs 等, 是就直接返回
根據文件路徑創建一個模塊對象
將該模塊加入模塊緩存中
通過對應的文件解析方式對文件進行解析編譯執行(node 默認僅支持解析.js
,.json
, .node
后綴的文件)
返回加載后的模塊 exports 對象
Module.prototype.require = function(id) { // ... try { // 主要通過Module的靜態方法_load加載模塊 return Module._load(id, this, /* isMain */ false); } finally {} // ... }; // ... Module._load = function(request, parent, isMain) { let relResolveCacheIdentifier; // ... // 解析文件路徑成絕對路徑 const filename = Module._resolveFilename(request, parent, isMain); // 查看當前需要加載的模塊是否已經有緩存 const cachedModule = Module._cache[filename]; // 如果有緩存, 則直接使用緩存的即可 if (cachedModule !== undefined) { // ... return cachedModule.exports; } // 查看是否是node自帶模塊, 如http,fs等, 是就直接返回 const mod = loadNativeModule(filename, request); if (mod && mod.canBeRequiredByUsers) return mod.exports; // 根據文件路徑初始化一個模塊 const module = cachedModule || new Module(filename, parent); // ... // 將該模塊加入模塊緩存中 Module._cache[filename] = module; if (parent !== undefined) { relativeResolveCache[relResolveCacheIdentifier] = filename; } // ... // 進行模塊的加載 module.load(filename); return module.exports; };
至此, node 的模塊原理流程基本過完了。目前 node v13.2.0 版本起已經正式支持 ESM 特性。
在接觸 node 中,你是否會困惑 __filename
, __dirname
是從哪里來的, 為什么會有這些變量呢? 仔細閱讀該章節,你會對這些有系統性的了解。
順著上面的 require 源碼繼續走, 當一個模塊加載時, 會對模塊內容讀取
將內容包裹成函數體
將拼接的函數字符串編譯成函數
執行編譯后的函數, 傳入對應的參數
Module.prototype._compile = function(content, filename) { // ... const compiledWrapper = wrapSafe(filename, content, this); // result = compiledWrapper.call(thisValue, exports, require, module, filename, dirname); // ... return result; };
function wrapSafe(filename, content, cjsModuleInstance) { // ... const wrapper = Module.wrap(content); // ... } let wrap = function(script) { return Module.wrapper[0] + script + Module.wrapper[1]; }; const wrapper = [ '(function (exports, require, module, __filename, __dirname) { ', '\n});' ]; ObjectDefineProperty(Module, 'wrap', { get() { return wrap; }, set(value) { patched = true; wrap = value; } });
綜上, 也就是之所以模塊里面有__dirname
,__filename
, module
, exports
, require
這些變量, 其實也就是 node 在執行過程傳入的, 看完是否解決了多年困惑的問題^_^
在package.json
增加"type": "module"配置
// test.mjs export default { a: 'xxx' } // import.js import a from './test.mjs'; console.log(a); // {a: 'xxx'}
較明顯的區別是在于執行時機:
ES 模塊在執行時會將所有import
導入的模塊會先進行預解析處理, 先于模塊內的其他模塊執行
// entry.js console.log('execute entry'); let a = require('./a.js') console.log(a); // a.js console.log('-----a--------'); module.exports = 'this is a'; // 最終輸出順序為: // execute entry // -----a-------- // this is a
// entry.js console.log('execute entry'); import b from './b.mjs'; console.log(b); // b.mjs console.log('-----b--------'); export default 'this is b'; // 最終輸出順序為: // -----b-------- // execute entry // this is b
import 只能在模塊的頂層,不能在代碼塊之中(比如在if
代碼塊中),如果需要動態引入, 需要使用import()
動態加載;
ES 模塊對比 CommonJS 模塊, 還有以下的區別:
沒有 require
、exports
或 module.exports
在大多數情況下,可以使用 ES 模塊 import 加載 CommonJS 模塊。(CommonJS 模塊文件后綴為 cjs)
如果需要引入.js
后綴的 CommonJS 模塊, 可以使用module.createRequire()
在 ES 模塊中構造require
函數
// test.cjs export default { a: 'xxx' } // import.js import a from './test.cjs'; console.log(a); // {a: 'xxx'}
// test.cjs export default { a: 'xxx' } // import.js import a from './test.cjs'; console.log(a); // {a: 'xxx'}
// test.cjs export default { a: 'xxx' } // import.mjs import { createRequire } from 'module'; const require = createRequire(import.meta.url); // test.js 是 CommonJS 模塊。 const siblingModule = require('./test'); console.log(siblingModule); // {a: 'xxx'}
沒有 __filename 或 __dirname
這些 CommonJS 變量在 ES 模塊中不可用。
沒有 JSON 模塊加載
JSON 導入仍處于實驗階段,僅通過 --experimental-json-modules 標志支持。
沒有 require.resolve
沒有 NODE_PATH
沒有 require.extensions
沒有 require.cache
在 CommonJS 中引入 ES 模塊
由于 ES Modules 的加載、解析和執行都是異步的,而 require() 的過程是同步的、所以不能通過 require() 來引用一個 ES6 模塊。
ES6 提議的 import() 函數將會返回一個 Promise,它在 ES Modules 加載后標記完成。借助于此,我們可以在 CommonJS 中使用異步的方式導入 ES Modules:
// b.mjs export default 'esm b' // entry.js (async () => { let { default: b } = await import('./b.mjs'); console.log(b); // esm b })()
在 ES 模塊中引入 CommonJS
在 ES6 模塊里可以很方便地使用 import 來引用一個 CommonJS 模塊,因為在 ES6 模塊里異步加載并非是必須的:
// a.cjs module.exports = 'commonjs a'; // entry.js import a from './a.cjs'; console.log(a); // commonjs a
至此,提供 2 個 demo 給大家測試下上述知識點是否已經掌握,如果沒有掌握可以回頭再進行閱讀。
demo module.exports&exports
// a模塊 exports.value = 2; // b模塊代碼 let a = require('./a'); console.log(a); // {value: 2}
demo module.exports&exports
// a模塊 exports = 2; // b模塊代碼 let a = require('./a'); console.log(a); // {}
require&_cache 模塊緩存機制
// origin.js let count = 0; exports.addCount = function () { count++ } exports.getCount = function () { return count; } // b.js let { getCount } = require('./origin'); exports.getCount = getCount; // a.js let { addCount, getCount: getValue } = require('./origin'); addCount(); console.log(getValue()); // 1 let { getCount } = require('./b'); console.log(getCount()); // 1
根據上述例子, 模塊在 require 引入時會加入緩存對象require.cache
中。 如果需要刪除緩存, 可以考慮將該緩存內容清除,則下次require
模塊將會重新加載模塊。
let count = 0; exports.addCount = function () { count++ } exports.getCount = function () { return count; } // b.js let { getCount } = require('./origin'); exports.getCount = getCount; // a.js let { addCount, getCount: getValue } = require('./origin'); addCount(); console.log(getValue()); // 1 delete require.cache[require.resolve('./origin')]; let { getCount } = require('./b'); console.log(getCount()); // 0
以上就是Nodejs中的模塊系統該如何使用,小編相信有部分知識點可能是我們日常工作會見到或用到的。希望你能通過這篇文章學到更多知識。更多詳情敬請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。