本篇文章給大家分享的是有關(guān)Nodejs中的模塊系統(tǒng)該如何使用,小編覺得挺實(shí)用的,因此分享給大家學(xué)習(xí),希望大家閱讀完這篇文章后可以有所收獲,話不多說(shuō),跟著小編一起來(lái)看看吧。
成都創(chuàng)新互聯(lián)是一家專業(yè)提供平武企業(yè)網(wǎng)站建設(shè),專注與成都網(wǎng)站制作、網(wǎng)站設(shè)計(jì)、html5、小程序制作等業(yè)務(wù)。10年已為平武眾多企業(yè)、政府機(jī)構(gòu)等服務(wù)。創(chuàng)新互聯(lián)專業(yè)網(wǎng)站建設(shè)公司優(yōu)惠進(jìn)行中。
早期 JavaScript 是為了實(shí)現(xiàn)簡(jiǎn)單的頁(yè)面交互邏輯, 但隨著時(shí)代發(fā)展, 瀏覽器不單單僅只能呈現(xiàn)簡(jiǎn)單交互, 各種各樣的網(wǎng)站開始大放光彩。隨著網(wǎng)站開始變得復(fù)雜化,前端代碼日漸增多,相對(duì)比起其他靜態(tài)語(yǔ)言,JavaScript 缺少模塊化的弊端開始暴露出來(lái),如命名沖突。因此為了方便前端代碼的維護(hù)和管理,社區(qū)開始了對(duì)模塊化規(guī)范的定義。在這過(guò)程中,出現(xiàn)了很多的模塊化規(guī)范,如CommonJS
, AMD
, CMD
, ES modules
,本文章主要講解Node
中根據(jù)CommonJS
實(shí)現(xiàn)的模塊化。
首先,在 Node 世界里,模塊系統(tǒng)是遵守CommonJS
規(guī)范的,CommonJS
規(guī)范里定義,簡(jiǎn)單講就是:
每一個(gè)文件就是一個(gè)模塊
通過(guò)module
對(duì)象來(lái)代表一個(gè)模塊的信息
通過(guò)exports
用來(lái)導(dǎo)出模塊對(duì)外暴露的信息
通過(guò)require
來(lái)引用一個(gè)模塊
核心模塊: 如 fs,http,path 等模塊, 這些模塊不需要安裝, 在運(yùn)行時(shí)已經(jīng)加載在內(nèi)存中。【推薦學(xué)習(xí):《nodejs 教程》】
第三方模塊: 通過(guò)安裝存放在 node_modules 中。
自定義模塊: 主要是指 file 模塊,通過(guò)絕對(duì)路徑或者相對(duì)路徑進(jìn)行引入。
我們上面說(shuō)過(guò),一個(gè)文件就是一個(gè)模塊,且通過(guò)一個(gè) module 對(duì)象來(lái)描述當(dāng)前模塊信息,一個(gè) module 對(duì)象對(duì)應(yīng)有以下屬性: - id: 當(dāng)前模塊的id - path: 當(dāng)前模塊對(duì)應(yīng)的路徑 - exports: 當(dāng)前模塊對(duì)外暴露的變量 - parent: 也是一個(gè)module對(duì)象,表示當(dāng)前模塊的父模塊,即調(diào)用當(dāng)前模塊的模塊 - filename: 當(dāng)前模塊的文件名(絕對(duì)路徑), 可用于在模塊引入時(shí)將加載的模塊加入到全局模塊緩存中, 后續(xù)引入直接從緩存里進(jìn)行取值 - loaded: 表示當(dāng)前模塊是否加載完畢 - children: 是一個(gè)數(shù)組,存放著當(dāng)前模塊調(diào)用的模塊 - paths: 是一個(gè)數(shù)組,記錄著從當(dāng)前模塊開始查找node_modules目錄,遞歸向上查找到根目錄下的node_modules目錄下
說(shuō)完CommonJS
規(guī)范,我們先講下module.exports
與exports
的區(qū)別。
首先,我們用個(gè)新模塊進(jìn)行一個(gè)簡(jiǎn)單驗(yàn)證
console.log(module.exports === exports); // true
可以發(fā)現(xiàn),module.exports
和epxorts
實(shí)際上就是指向同一個(gè)引用變量。
demo1
// a模塊 module.exports.text = 'xxx'; exports.value = 2; // b模塊代碼 let a = require('./a'); console.log(a); // {text: 'xxx', value: 2}
從而也就驗(yàn)證了上面demo1
中,為什么通過(guò)module.exports
和exports
添加屬性,在模塊引入時(shí)兩者都存在, 因?yàn)閮烧咦罱K都是往同一個(gè)引用變量上面進(jìn)行屬性的添加.根據(jù)該 demo, 可以得出結(jié)論: module.exports
和exports
指向同一個(gè)引用變量
demo2
// a模塊 module.exports = { text: 'xxx' } exports.value = 2; // b模塊代碼 let a = require('./a'); console.log(a); // {text: 'xxx'}
上面的 demo 例子中,對(duì)module.exports
進(jìn)行了重新賦值, exports
進(jìn)行了屬性的新增, 但是在引入模塊后最終導(dǎo)出的是module.exports
定義的值, 可以得出結(jié)論: noed 的模塊最終導(dǎo)出的是module.exports
, 而exports
僅僅是對(duì) module.exports 的引用, 類似于以下代碼:
exports = module.exports = {}; (function (exports, module) { // a模塊里面的代碼 module.exports = { text: 'xxx' } exports.value = 2; console.log(module.exports === exports); // false })(exports, module)
由于在函數(shù)執(zhí)行中, exports 僅是對(duì)原module.exports
對(duì)應(yīng)變量的一個(gè)引用,當(dāng)對(duì)module.exports
進(jìn)行賦值時(shí),exports
對(duì)應(yīng)的變量和最新的module.exports
并不是同一個(gè)變量
require
引入模塊的過(guò)程主要分為以下幾步:
解析文件路徑成絕對(duì)路徑
查看當(dāng)前需要加載的模塊是否已經(jīng)有緩存, 如果有緩存, 則直接使用緩存的即可
查看是否是 node 自帶模塊, 如 http,fs 等, 是就直接返回
根據(jù)文件路徑創(chuàng)建一個(gè)模塊對(duì)象
將該模塊加入模塊緩存中
通過(guò)對(duì)應(yīng)的文件解析方式對(duì)文件進(jìn)行解析編譯執(zhí)行(node 默認(rèn)僅支持解析.js
,.json
, .node
后綴的文件)
返回加載后的模塊 exports 對(duì)象
Module.prototype.require = function(id) { // ... try { // 主要通過(guò)Module的靜態(tài)方法_load加載模塊 return Module._load(id, this, /* isMain */ false); } finally {} // ... }; // ... Module._load = function(request, parent, isMain) { let relResolveCacheIdentifier; // ... // 解析文件路徑成絕對(duì)路徑 const filename = Module._resolveFilename(request, parent, isMain); // 查看當(dāng)前需要加載的模塊是否已經(jīng)有緩存 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; // 根據(jù)文件路徑初始化一個(gè)模塊 const module = cachedModule || new Module(filename, parent); // ... // 將該模塊加入模塊緩存中 Module._cache[filename] = module; if (parent !== undefined) { relativeResolveCache[relResolveCacheIdentifier] = filename; } // ... // 進(jìn)行模塊的加載 module.load(filename); return module.exports; };
至此, node 的模塊原理流程基本過(guò)完了。目前 node v13.2.0 版本起已經(jīng)正式支持 ESM 特性。
在接觸 node 中,你是否會(huì)困惑 __filename
, __dirname
是從哪里來(lái)的, 為什么會(huì)有這些變量呢? 仔細(xì)閱讀該章節(jié),你會(huì)對(duì)這些有系統(tǒng)性的了解。
順著上面的 require 源碼繼續(xù)走, 當(dāng)一個(gè)模塊加載時(shí), 會(huì)對(duì)模塊內(nèi)容讀取
將內(nèi)容包裹成函數(shù)體
將拼接的函數(shù)字符串編譯成函數(shù)
執(zhí)行編譯后的函數(shù), 傳入對(duì)應(yīng)的參數(shù)
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
這些變量, 其實(shí)也就是 node 在執(zhí)行過(guò)程傳入的, 看完是否解決了多年困惑的問(wèn)題^_^
在package.json
增加"type": "module"配置
// test.mjs export default { a: 'xxx' } // import.js import a from './test.mjs'; console.log(a); // {a: 'xxx'}
較明顯的區(qū)別是在于執(zhí)行時(shí)機(jī):
ES 模塊在執(zhí)行時(shí)會(huì)將所有import
導(dǎo)入的模塊會(huì)先進(jìn)行預(yù)解析處理, 先于模塊內(nèi)的其他模塊執(zhí)行
// entry.js console.log('execute entry'); let a = require('./a.js') console.log(a); // a.js console.log('-----a--------'); module.exports = 'this is a'; // 最終輸出順序?yàn)? // 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'; // 最終輸出順序?yàn)? // -----b-------- // execute entry // this is b
import 只能在模塊的頂層,不能在代碼塊之中(比如在if
代碼塊中),如果需要?jiǎng)討B(tài)引入, 需要使用import()
動(dòng)態(tài)加載;
ES 模塊對(duì)比 CommonJS 模塊, 還有以下的區(qū)別:
沒(méi)有 require
、exports
或 module.exports
在大多數(shù)情況下,可以使用 ES 模塊 import 加載 CommonJS 模塊。(CommonJS 模塊文件后綴為 cjs)
如果需要引入.js
后綴的 CommonJS 模塊, 可以使用module.createRequire()
在 ES 模塊中構(gòu)造require
函數(shù)
// 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'}
沒(méi)有 __filename 或 __dirname
這些 CommonJS 變量在 ES 模塊中不可用。
沒(méi)有 JSON 模塊加載
JSON 導(dǎo)入仍處于實(shí)驗(yàn)階段,僅通過(guò) --experimental-json-modules 標(biāo)志支持。
沒(méi)有 require.resolve
沒(méi)有 NODE_PATH
沒(méi)有 require.extensions
沒(méi)有 require.cache
在 CommonJS 中引入 ES 模塊
由于 ES Modules 的加載、解析和執(zhí)行都是異步的,而 require() 的過(guò)程是同步的、所以不能通過(guò) require() 來(lái)引用一個(gè) ES6 模塊。
ES6 提議的 import() 函數(shù)將會(huì)返回一個(gè) Promise,它在 ES Modules 加載后標(biāo)記完成。借助于此,我們可以在 CommonJS 中使用異步的方式導(dǎo)入 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 來(lái)引用一個(gè) CommonJS 模塊,因?yàn)樵?ES6 模塊里異步加載并非是必須的:
// a.cjs module.exports = 'commonjs a'; // entry.js import a from './a.cjs'; console.log(a); // commonjs a
至此,提供 2 個(gè) demo 給大家測(cè)試下上述知識(shí)點(diǎn)是否已經(jīng)掌握,如果沒(méi)有掌握可以回頭再進(jìn)行閱讀。
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 模塊緩存機(jī)制
// 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
根據(jù)上述例子, 模塊在 require 引入時(shí)會(huì)加入緩存對(duì)象require.cache
中。 如果需要?jiǎng)h除緩存, 可以考慮將該緩存內(nèi)容清除,則下次require
模塊將會(huì)重新加載模塊。
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中的模塊系統(tǒng)該如何使用,小編相信有部分知識(shí)點(diǎn)可能是我們?nèi)粘9ぷ鲿?huì)見到或用到的。希望你能通過(guò)這篇文章學(xué)到更多知識(shí)。更多詳情敬請(qǐng)關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。
當(dāng)前標(biāo)題:Nodejs中的模塊系統(tǒng)該如何使用
文章URL:http://bm7419.com/article18/jcsjgp.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供外貿(mào)建站、網(wǎng)頁(yè)設(shè)計(jì)公司、關(guān)鍵詞優(yōu)化、搜索引擎優(yōu)化、用戶體驗(yàn)、軟件開發(fā)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)