談?wù)刵ode.js中的模塊系統(tǒng)
Node.js 的模塊
JavaScript 做為一門為網(wǎng)頁添加交互功能的簡單腳本語言問世,在誕生時并不包含模塊系統(tǒng),隨著 JavaScript 解決問題越來越復(fù)雜,把所有代碼寫在一個文件內(nèi),用 function 區(qū)分功能單元已經(jīng)不能支撐復(fù)雜應(yīng)用開發(fā)了,ES6 帶來了大部分高級語言都有的 class 和 module,方便開發(fā)者組織代碼
import _ from 'lodash';
class Fun {}
export default Fun;
上面三行代碼展示了一個模塊系統(tǒng)最重要的兩個要素 import 和 export
1. export用于規(guī)定模塊的對外接口
2. import用于輸入其他模塊提供的功能
而在 ES6 之前,社區(qū)出現(xiàn)了很多模塊加載方案,最主要的有 CommonJS 和 AMD 兩種,Node.js 誕生早于 ES6,模塊系統(tǒng)使用的是類似 CommonJS 的實現(xiàn),遵從幾個原則
1. 一個文件是一個模塊,文件內(nèi)的變量作用域都在模塊內(nèi)
2. 使用 module.exports 對象導(dǎo)出模塊對外接口
3. 使用 require 引入其它模塊
circle.js
const { PI } = Math;
module.exports = function area(r) {
PI * r ** 2;
};
上面代碼就實現(xiàn)了 Node.js 的一個模塊,模塊沒有依賴其它模塊,導(dǎo)出了方法 area 計算圓的面積
test.js
const area = require('./circle.js');
console.log(`半徑為 4 的圓的面積是 ${area(4)}`);
模塊依賴了 circle.js,使用其對外暴露的 area 方法,計算圓的面積
module.exports
模塊對外暴露接口使用 module.exports,常見的有兩種用法:為其添加屬性或賦值到新對象
test.js
// 添加屬性
module.exports.prop1 = xxx;
module.exports.funA = xxx;
module.exports.funB = xxx;
// 賦值到全新對象
module.exports = {
prop1,
funA,
funB,
};
兩種寫法是等價的,使用時候沒區(qū)別
const mod = require('./test.js');
console.log(mod.prop1);
console.log(mod.funA());
還有另外一種直接使用 exports 對象的方法,但是只能對其添加屬性,不能賦值到新對象,后面會介紹原因
// 正確的寫法:添加屬性
exports.prop1 = xxx;
exports.funA = xxx;
exports.funB = xxx;
// 賦值到全新對象
module.exports = {
prop1,
funA,
funB,
};
require('id')
模塊類型
require 用法比較簡單,id 支持模塊名和文件路徑兩種類型
模塊名
const fs = require('fs');
const _ = require('lodash');
示例中的 fs、lodash 都是模塊名,fs 是 Node.js 內(nèi)置的核心模塊,lodash 是通過 npm 安裝到 node_modules 下的第三方模塊,如果出現(xiàn)重名,優(yōu)先使用系統(tǒng)內(nèi)置模塊
因為一個項目內(nèi)可能會包含多個 node_modules 文件夾(Node.js 比較失敗的設(shè)計),第三方模塊查找過程會遵循就近原則逐層上溯(可以在程序中打印 module.paths 查看具體查找路徑),直到根據(jù) NODE_PATH 環(huán)境變量查找到文件系統(tǒng)根目錄,具體過程可以參考官方文檔
此外,Node.js 還會搜索以下的全局目錄列表:
- $HOME/.node_modules
- $HOME/.node_libraries
- $PREFIX/lib/node
其中 $HOME 是用戶的主目錄, $PREFIX 是 Node.js 里配置的 node_prefix。強(qiáng)烈建議將所有的依賴放在本地的 node_modules 目錄,這樣將會更快地加載,且更可靠
文件路徑
模塊還可以可以使用文件路徑加載,這是項目內(nèi)自定義模塊的通用加載方式,路徑可以省略拓展名,會按照 .js、.json、.node 順序嘗試
- 以 '/' 為前綴的模塊是文件的絕對路徑,按照系統(tǒng)路徑查找模塊
- 以 './' 為前綴的模塊是相對于當(dāng)前調(diào)用 require 方法的文件,不受后續(xù)模塊在哪里被使用到影響
單次加載 & 循環(huán)依賴
模塊在第一次加載后會被緩存到 Module._cache ,如果每次調(diào)用 require('foo') 都解析到同一文件,則返回相同的對象,同時多次調(diào)用 require(foo) 不會導(dǎo)致模塊的代碼被執(zhí)行多次。 Node.js 根據(jù)實際的文件名緩存模塊,因此從不同層級目錄引用相同模塊不會重復(fù)加載。
理解的模塊單次加載機(jī)制方便我們理解模塊循環(huán)依賴后的現(xiàn)象
a.js
console.log('a 開始');
exports.done = false;
const b = require('./b.js');
console.log('在 a 中,b.done = %j', b.done);
exports.done = true;
console.log('a 結(jié)束');
b.js
console.log('b 開始');
exports.done = false;
const a = require('./a.js');
console.log('在 b 中,a.done = %j', a.done);
exports.done = true;
console.log('b 結(jié)束');
main.js
console.log('main 開始');
const a = require('./a.js');
const b = require('./b.js');
console.log('在 main 中,a.done=%j,b.done=%j', a.done, b.done);
當(dāng) main.js 加載 a.js 時,a.js 又加載 b.js,此時,b.js 會嘗試去加載 a.js
為了防止無限的循環(huán)會返回一個 a.js 的 exports 對象的 未完成的副本 給 b.js 模塊,然后 b.js 完成加載,并將 exports 對象提供給 a.js 模塊
因此示例的輸出是
main 開始
a 開始
b 開始
在 b 中,a.done = false
b 結(jié)束
在 a 中,b.done = true
a 結(jié)束
在 main 中,a.done=true,b.done=true
看不懂上面的過程也沒關(guān)系,日常工作根本用不到,即使看懂了也不要在項目中使用循環(huán)依賴!
工作原理
Node.js 每個文件都是一個模塊,模塊內(nèi)的變量都是局部變量,不會污染全局變量,在執(zhí)行模塊代碼之前,Node.js 會使用一個如下的函數(shù)封裝器將模塊封裝
(function(exports, require, module, __filename, __dirname) {
// 模塊的代碼實際上在這里
});
- __filename:當(dāng)前模塊文件的絕對路徑
- __dirname:當(dāng)前模塊文件據(jù)所在目錄的絕對路徑
- module:當(dāng)前的模塊實例
- require:加載其它模塊的方法,module.require 的快捷方式
- exports:導(dǎo)出模塊接口的對象,module.exports 的快捷方式
回頭看看最開始的問題,為什么 exports 對象不支持賦值為其它對象?把上面函數(shù)添加一句 exports 對象來源就很簡單了
const exports = module.exports;
(function(exports, require, module, __filename, __dirname) {
// 模塊的代碼實際上在這里
});
其它模塊 require 到的肯定是模塊的 module.exports 對象,如果吧 exports 對象賦值給其它對象,就和 module.exports 對象斷開了連接,自然就沒用了
在 Node.js 中使用 ES Module
隨著 ES6 使用越來越廣泛,Node.js 也支持了 ES6 Module,有幾種方法
babel 構(gòu)建
使用 babel 構(gòu)建是在 v12 之前版本最簡單、通用的方式,具體配置參考 @babel/preset-env
.babelrc
{
"presets": [
["@babel/preset-env", {
"targets": {
"node": "8.9.0",
"esmodules": true
}
}]
]
}
原生支持
在 v12 后可以使用原生方式支持 ES Module
- 開啟
--experimental-modules - 模塊名修改為
.mjs(強(qiáng)烈不推薦使用)或者 package.json 中設(shè)置"type": module
這樣 Node.js 會把 js 文件都當(dāng)做 ES Module 來處理,更多詳情參考官方文檔
以上就是談?wù)刵ode.js中的模塊系統(tǒng)的詳細(xì)內(nèi)容,更多關(guān)于node.js 模塊的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
小結(jié)Node.js中非阻塞IO和事件循環(huán)
本文針對在Node.js關(guān)鍵的兩個概念:非阻塞IO和事件循環(huán)進(jìn)行了適當(dāng)?shù)目偨Y(jié),需要的朋友可以參考下2014-09-09
nodejs?express路由匹配控制及Router模塊化使用詳解
這篇文章主要為大家介紹了nodejs?express路由匹配控制及Router模塊化使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10
在CentOS 7服務(wù)器上安裝Node.js的方法步驟
Node.js 是一個用于服務(wù)器端編程的 JavaScript 平臺,允許用戶快速構(gòu)建網(wǎng)絡(luò)應(yīng)用程序,通過在前端和后端都使用 JavaScript,開發(fā)可以更加一致并且可以在同一個系統(tǒng)中設(shè)計,在本指南中,我們將向您展示如何在 Ubuntu 14.04 服務(wù)器上開始使用 Node.js2024-09-09
Windows系統(tǒng)下Node.js安裝以及環(huán)境配置的完美教程
相信對于很多關(guān)注javascript發(fā)展的同學(xué)來說,nodejs已經(jīng)不是一個陌生的詞眼,下面這篇文章主要給大家介紹了關(guān)于Windows系統(tǒng)下Node.js安裝以及環(huán)境配置的完美教程,需要的朋友可以參考下2022-06-06

