NodeJS學(xué)習(xí)筆記之Module的簡介
Node.js模塊系統(tǒng)
Node.js有一個簡單的模塊加載系統(tǒng)。 在Node.js中,文件和模塊是一一對應(yīng)的(每個文件被視為單獨(dú)的模塊)。
例如,考慮下面這個名為 foo.js 的文件:
const circle = require('./circle.js');
console.log(`The area of a circle of radius 4 is ${circle.area(4)}`);
在第一行, foo.js 加載與 foo.js 同一目錄的模塊 circle.js 。
circle.js 的內(nèi)容如下:
const PI = Math.PI; exports.area = (r) => PI * r * r; exports.circumference = (r) => 2* PI * r;
模塊 circle.js 導(dǎo)出了函數(shù) area() 和 circumference() 。 要將函數(shù)和對象添加到模塊的根目錄,可以將它們賦值到特殊 exports 對象上。
模塊內(nèi)部的變量一定是私有的,因?yàn)槟K被Node.js包裹在一個函數(shù)中(參見下面的模塊包裝器)。 在這個例子中,變量 PI 對于 circle.js 來說是私有變量。
如果你希望模塊導(dǎo)出的是一個函數(shù)(如構(gòu)造函數(shù)),或者是要導(dǎo)出完整的對象,而不是一次創(chuàng)建一個屬性,則需要將其分配給 module.exports 而不是 exports 。
在下面的 bar.js 中,使用了 square 模塊,它導(dǎo)出一個構(gòu)造函數(shù):
const square = require('./square.js');
var mySquare = square(2);
console.log(`The area of my square is ${mySquare.area()}`);
在 square.js 模塊中定義一個 square 方法:
module.exports = (width) => {
return {
area: () => width * width;
};
}
此外,模塊系統(tǒng)在 require(“module”) 模塊中實(shí)現(xiàn)。
『main』模塊
當(dāng)某個 module 直接從Node.js運(yùn)行時,它會將 require.main 設(shè)置該 module 。 你可以通過這個來測試這個 module 是被直接運(yùn)行的還是被 require 的。
require.main === module
就拿文件 foo.js 來說,如果運(yùn)行 node foo.js 這個屬性就是 true 。運(yùn)行 require('./foo.js') 就是 false 。
因?yàn)?module 提供了一個 filename (通常相當(dāng)于 __filename ),因此可以通過檢查 require.main.filename 來獲取當(dāng)前應(yīng)用程序的入口點(diǎn)。
包管理器的一些提示
Node.js的 require() 函數(shù)支持一些合理的目錄結(jié)構(gòu)。它讓軟件包管理器程序(如 dpkg , rpm 和 npm )可以從Node.js模塊中直接去構(gòu)建本地的包而不需要修改。
下面我們給出一個可以正常工作的建議目錄結(jié)構(gòu):
假設(shè)我們希望在 /usr/lib/node/<some-package>/<some-version> 中的文件夾來指定版本的包。
此外,包還可以相互依賴。 比如你想安裝 foo 包,而這個包有可能需要安裝指定版本的 bar 包。而 bar 包也很有可能依賴其他的包,并且在某些特殊情況下,這些依賴包甚至可能會產(chǎn)生循環(huán)依賴。
由于Node.js會查找加載的所有模塊的 realpath (即解析軟鏈),然后再去node_modules文件夾中查找依賴的包,因此使用以下方案可以非常簡單地解決此問題:
/usr/lib/node/foo/1.2.3/ - 包含 foo 包,版本是 1.2.3
/usr/lib/node/bar/4.3.2/ - 包含 foo 所依賴的 bar 包
/usr/lib/node/foo/1.2.3/node_modules/bar - 軟鏈到 /usr/lib/node/bar/4.3.2/
/usr/lib/node/bar/4.3.2/node_modules/* - 軟鏈到 bar 的依賴
因此,即使遇到循環(huán)依賴,或者是依賴沖突,每個模塊都能加載到并使用自己所依賴指定版本的包。
當(dāng) foo 包中 require('bar') 時,它就可以軟鏈到指定版本的 /usr/lib/node/foo/1.2.3/node_modules/bar 。然后,當(dāng) bar 包中的代碼調(diào)用 require('quux') 時,它同樣也可以軟鏈到指定版本的 /usr/lib/node/bar/4.3.2/node_modules/quux 。
模塊加載的全過程(重點(diǎn),下面寫的偽代碼流程一定要記?。?/p>
要獲取在調(diào)用 require() 將被加載的確切文件名,請使用 require.resolve() 函數(shù)。
以下是模塊加載的全過程以及 require.resolve 的解析過程:
// 加載X模塊 require(X) from module at path Y 1. If X is a core module. a. return the core module b. STOP 2. If X begins with './' or '/' or '../' a. LOAD_AS_FILE(Y + X) b. LOAD_AS_DIRECTORY(Y + X) 3. LOAD_NODE_MODULES(X, dirname(Y)) 4. THROW "not found" // 加載X文件 // 加載過程:X -> X.js -> X.json -> X.node LOAD_AS_FILE(X) 1. If [X] is a file, load [X] as JavaScript text. STOP 2. If [X.js] is a file, load [X.js] as JavaScript text. STOP 3. If [X.json] is a file, load [X.json] as JavaScript text. STOP 4. If [X.node] is a file, load [X.node] as JavaScript text. STOP // 加載入口文件 // 加載過程:X -> X/index.js -> X/index.json -> X/index.node LOAD_INDEX(X) 1. If [X/index.js] is a file, load [X/index.js] as JavaScript text. STOP 2. If [X/index.json] is a file, load [X/index.json] as JavaScript text. STOP 3. If [X/index.node] if a file, load [X/index.node] as JavaScript text. STOP // 加載文件夾 LOAD_AS_DIRECTORY(X) 1. If [X/package.json] is a file. a. Parse [X/package.json], and look for "main" field b. let M = X + (json main field) c. LOAD_AS_FILE(M) d. LOAD_INDEX(M) 2. LOAD_INDEX(X) // 加載node模塊 LOAD_NODE_MODULES(X, START) 1. let DIRS = NODE_MODULES_PATHS(START) 2. for each DIR in DIRS; a. LOAD_AS_FILE(DIR/X) b. LOAD_AS_DIRECTORY(DIR/X) // 列出所有可能的node_modules路徑 NODE_MODULES_PATHS(START) 1. let PARTS = path split(START); 2. let I = count of PARTS - 1 3. let DIRS = [] 4. while I > 0 a. If PARTS[I] = "node_modules" CONTINUE b. DIR = path join(PARTS[0 ... I] + "node_modules") c. DIRS = DIRS + DIR d. let I = I -1 5. return DIRS
模塊緩存
所有的模塊都會在第一次加載之后被緩存起來。 這意味著你每次調(diào)用 require('foo') 將得到完全相同的對象。
對 require('foo') 的多次調(diào)用可能并不會多次執(zhí)行該模塊的代碼。 這是一個重要的功能。 使用它,可以返回“partially done”對象,從而允許根據(jù)依賴關(guān)系一層一層地加載模塊,即使這樣做可能會導(dǎo)致循環(huán)依賴。
如果要讓某個模塊在每次被加載時都去執(zhí)行代碼,則需要 exports 一個函數(shù),并調(diào)用該函數(shù)即可。
模塊緩存注意事項(xiàng)
模塊是基于其解析出來的文件名進(jìn)行緩存。根據(jù)調(diào)用模塊的路徑,被調(diào)用的模塊可能會解析出不同的文件名(從node_modules文件夾加載)。如果解析出來的是不同的文件,它不保證每次 require('foo') 總是返回相同的對象。
另外,在不區(qū)分大小寫的文件系統(tǒng)或操作系統(tǒng)上,不同的解析文件名可以指向相同的文件,但緩存仍將它們視為不同的模塊,并將重新加載該文件多次。 例如, require('./ foo') 和 require('./ FOO') 返回兩個不同的對象,而不管 ./foo 和 ./FOO 是否是同一個文件。
核心模塊
Node.js有些模塊被編譯成二進(jìn)制文件。 本文檔中的其他部分將對這些模塊進(jìn)行更詳細(xì)的描述。
核心模塊在Node.js的源碼 lib/ 文件夾中。
如果核心模塊的模塊標(biāo)識傳遞給 require() ,則它們總是優(yōu)先加載。 例如,即使有一個自定義模塊叫 http ,我們?nèi)?zhí)行 require('http') 也將始終返回內(nèi)置的 HTTP 模塊,
循環(huán)引用
當(dāng)循環(huán)引用 require() 時,返回模塊可能并沒有執(zhí)行完成。
考慮這種情況:
a.js :
console.log('a starting');
exports.done = false;
const b = require('./b.js');
console.log('in a, b.done = %j', b.done);
exports.done = true;
console.log('a done');
b.js :
console.log('b starting');
exports.done = false;
const a = require('./a.js');
console.log('in b, a.done = %j', a.done);
exports.done = true;
console.log('b done');
app.js :
console.log('main starting');
const a = require('./a.js');
const b = require('./b.js');
console.log('in main, a.done = %j, b.done = %j', a.done, b.done);
當(dāng) app.js 加載 a.js 時, a.js 依次加載 b.js . 此時, b.js 嘗試加載 a.js . 為了防止無限循環(huán),將 a.js 導(dǎo)出對象的未完成副本返回到 b.js 模塊。 b.js 然后完成加載,并將其導(dǎo)出對象提供給 a.js 模塊。
當(dāng) app.js 加載了這兩個模塊時,它們都已經(jīng)完成。 因此,該程序的輸出將是:
$ node app.js main starting a starting b starting in b, a.done = false b done in a, b.done = true in main, a.done =true, b.done = true
模塊包裝器
在執(zhí)行模塊的代碼之前,Node.js將使用一個函數(shù)包裝器來將模塊內(nèi)容包裹起來,如下所示:
(function (exports, require, module, __filename, __dirname) {
// 你的模塊代碼
});
通過這樣做,Node.js實(shí)現(xiàn)了以下幾點(diǎn):
它將模塊內(nèi)部的頂級變量(定義為 var , const 或 let )的作用域范圍限定為模塊內(nèi)部而不是全局。
它有助于給模塊內(nèi)部提供一些實(shí)際上只屬于該模塊的全局變量,例如:
module 和 exports 對象用來幫助從模塊內(nèi)部導(dǎo)出一些值
變量 __filename 和 __dirname 是當(dāng)前模塊最終解析出來的文件名和文件夾路徑
module 對象簽名
Object module {
id: String, // 模塊標(biāo)識,為該模塊文件在系統(tǒng)中的絕對路徑
exports: Object, // 該模塊的導(dǎo)出對象
parent: Object | undefined, // 引用該模塊的父模塊
filename: String | null, // 最終解析的文件名稱, 與__filename相同。
loaded: Boolean, // 該模塊是否已經(jīng)加載
children: Array, // 改模塊的引用列表
paths: Array // 模塊加載路徑
}
require 函數(shù)簽名
Function require {
[Function], // 函數(shù)體
resolve: Function, // 根據(jù)模塊標(biāo)識解析模塊,返回絕對路徑
main: undefined | Object, // 應(yīng)用的主(main)模塊
extensions: {'.js':Function, '.json':Function, '.node':Function},
cache: Object // 模塊緩存,以模塊的絕對路徑為key
}
- Node.js學(xué)習(xí)教程之Module模塊
- vue中node_modules中第三方模塊的修改使用詳解
- 深入理解Node module模塊
- nodejs中exports與module.exports的區(qū)別詳細(xì)介紹
- node.js中module.exports與exports用法上的區(qū)別
- 深入理解node exports和module.exports區(qū)別
- node中modules.exports與exports導(dǎo)出的區(qū)別
- 詳解Node.js中exports和module.exports的區(qū)別
- Node.js 中exports 和 module.exports 的區(qū)別
- 淺談node中的exports與module.exports的關(guān)系
- node.js中module模塊的功能理解與用法實(shí)例分析
相關(guān)文章
Node.js 實(shí)現(xiàn)遠(yuǎn)程桌面監(jiān)控的方法步驟
這篇文章主要介紹了Node.js 實(shí)現(xiàn)遠(yuǎn)程桌面監(jiān)控的方法步驟,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-07-07
Node.js事件循環(huán)(Event Loop)和線程池詳解
這篇文章主要介紹了Node.js事件循環(huán)(Event Loop)和線程池詳解,這篇文章比較淺顯地探討了有關(guān)事件循環(huán)的內(nèi)部運(yùn)作機(jī)制和技術(shù)細(xì)節(jié),都是經(jīng)過深思熟慮的,需要的朋友可以參考下2015-01-01
基于Express和Multer實(shí)現(xiàn)文件本地服務(wù)器文件上傳功能
在現(xiàn)代應(yīng)用程序中,文件上傳功能成為了用戶共享和存儲數(shù)據(jù)的重要途徑,所以本文我們一起來探討文件上傳中間件的重要性,并提供常見的實(shí)現(xiàn)方法和相應(yīng)的代碼吧2023-06-06
Node.js實(shí)現(xiàn)壓縮與解壓數(shù)據(jù)
這篇文章介紹了Node.js實(shí)現(xiàn)壓縮與解壓數(shù)據(jù)的方法,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-07-07
node.js+jQuery實(shí)現(xiàn)用戶登錄注冊AJAX交互
本篇文章主要介紹了用Node.js當(dāng)作后臺、jQuery寫前臺AJAX代碼實(shí)現(xiàn)用戶登錄和注冊的功能的相關(guān)知識。具有很好的參考價值。下面跟著小編一起來看下吧2017-04-04

