溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Nodejs中的模塊系統(tǒng)該如何使用

發(fā)布時(shí)間:2021-09-14 11:08:41 來(lái)源:億速云 閱讀:157 作者:柒染 欄目:web開(kāi)發(fā)

本篇文章給大家分享的是有關(guān)Nodejs中的模塊系統(tǒng)該如何使用,小編覺(jué)得挺實(shí)用的,因此分享給大家學(xué)習(xí),希望大家閱讀完這篇文章后可以有所收獲,話不多說(shuō),跟著小編一起來(lái)看看吧。

模塊化的背景

早期 JavaScript 是為了實(shí)現(xiàn)簡(jiǎn)單的頁(yè)面交互邏輯, 但隨著時(shí)代發(fā)展, 瀏覽器不單單僅只能呈現(xiàn)簡(jiǎn)單交互, 各種各樣的網(wǎng)站開(kāi)始大放光彩。隨著網(wǎng)站開(kāi)始變得復(fù)雜化,前端代碼日漸增多,相對(duì)比起其他靜態(tài)語(yǔ)言,JavaScript 缺少模塊化的弊端開(kāi)始暴露出來(lái),如命名沖突。因此為了方便前端代碼的維護(hù)和管理,社區(qū)開(kāi)始了對(duì)模塊化規(guī)范的定義。在這過(guò)程中,出現(xiàn)了很多的模塊化規(guī)范,如CommonJS, AMD, CMD, ES modules,本文章主要講解Node中根據(jù)CommonJS實(shí)現(xiàn)的模塊化。

CommonJS 規(guī)范

首先,在 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è)模塊

Node 模塊分類

  • 核心模塊: 如 fs,http,path 等模塊, 這些模塊不需要安裝, 在運(yùn)行時(shí)已經(jīng)加載在內(nèi)存中?!就扑]學(xué)習(xí):《nodejs 教程》】

  • 第三方模塊: 通過(guò)安裝存放在 node_modules 中。

  • 自定義模塊: 主要是指 file 模塊,通過(guò)絕對(duì)路徑或者相對(duì)路徑進(jìn)行引入。

Module 對(duì)象

我們上面說(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)前模塊開(kāi)始查找node_modules目錄,遞歸向上查找到根目錄下的node_modules目錄下

module.exports 與 exports

說(shuō)完CommonJS規(guī)范,我們先講下module.exportsexports的區(qū)別。

首先,我們用個(gè)新模塊進(jìn)行一個(gè)簡(jiǎn)單驗(yàn)證

console.log(module.exports === exports); // true

可以發(fā)現(xiàn),module.exportsepxorts實(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.exportsexports添加屬性,在模塊引入時(shí)兩者都存在, 因?yàn)閮烧咦罱K都是往同一個(gè)引用變量上面進(jìn)行屬性的添加.根據(jù)該 demo, 可以得出結(jié)論: module.exportsexports指向同一個(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 方法

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ì)象

Nodejs中的模塊系統(tǒng)該如何使用

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 特性。

__filename, __dirname

在接觸 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)題^_^

NodeJS 中使用 ES Modules

  • package.json增加"type": "module"配置

// test.mjs
export default {
	a: 'xxx'
}
// import.js
import a from './test.mjs';

console.log(a); // {a: 'xxx'}

import 與 require 兩種機(jī)制的區(qū)別

較明顯的區(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、exportsmodule.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

ES 模塊和 CommonJS 的相互引用

在 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

require.cache

根據(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ì)見(jiàn)到或用到的。希望你能通過(guò)這篇文章學(xué)到更多知識(shí)。更多詳情敬請(qǐng)關(guān)注億速云行業(yè)資訊頻道。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI