Node.js模塊系統(tǒng)
專注于為中小企業(yè)提供做網(wǎng)站、成都做網(wǎng)站服務(wù),電腦端+手機(jī)端+微信端的三站合一,更高效的管理,為中小企業(yè)東湖免費(fèi)做網(wǎng)站提供優(yōu)質(zhì)的服務(wù)。我們立足成都,凝聚了一批互聯(lián)網(wǎng)行業(yè)人才,有力地推動(dòng)了上1000家企業(yè)的穩(wěn)健成長,幫助中小企業(yè)通過網(wǎng)站建設(shè)實(shí)現(xiàn)規(guī)模擴(kuò)充和轉(zhuǎn)變。
Node.js有一個(gè)簡單的模塊加載系統(tǒng)。 在Node.js中,文件和模塊是一一對應(yīng)的(每個(gè)文件被視為單獨(dú)的模塊)。
例如,考慮下面這個(gè)名為 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包裹在一個(gè)函數(shù)中(參見下面的模塊包裝器)。 在這個(gè)例子中,變量 PI 對于 circle.js 來說是私有變量。
如果你希望模塊導(dǎo)出的是一個(gè)函數(shù)(如構(gòu)造函數(shù)),或者是要導(dǎo)出完整的對象,而不是一次創(chuàng)建一個(gè)屬性,則需要將其分配給 module.exports
而不是 exports 。
在下面的 bar.js 中,使用了 square
模塊,它導(dǎo)出一個(gè)構(gòu)造函數(shù):
const square = require('./square.js'); var mySquare = square(2); console.log(`The area of my square is ${mySquare.area()}`);
在 square.js 模塊中定義一個(gè) square 方法:
module.exports = (width) => { return { area: () => width * width; }; }
此外,模塊系統(tǒng)在 require(“module”) 模塊中實(shí)現(xiàn)。
『main』模塊
當(dāng)某個(gè) module 直接從Node.js運(yùn)行時(shí),它會(huì)將 require.main 設(shè)置該 module 。 你可以通過這個(gè)來測試這個(gè) module 是被直接運(yùn)行的還是被 require 的。
require.main === module
就拿文件 foo.js 來說,如果運(yùn)行 node foo.js 這個(gè)屬性就是 true 。運(yùn)行 require('./foo.js') 就是 false 。
因?yàn)?module 提供了一個(gè) 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)建本地的包而不需要修改。
下面我們給出一個(gè)可以正常工作的建議目錄結(jié)構(gòu):
假設(shè)我們希望在 /usr/lib/node/
此外,包還可以相互依賴。 比如你想安裝 foo 包,而這個(gè)包有可能需要安裝指定版本的 bar 包。而 bar 包也很有可能依賴其他的包,并且在某些特殊情況下,這些依賴包甚至可能會(huì)產(chǎn)生循環(huán)依賴。
由于Node.js會(huì)查找加載的所有模塊的 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)依賴,或者是依賴沖突,每個(gè)模塊都能加載到并使用自己所依賴指定版本的包。
當(dāng) foo 包中 require('bar') 時(shí),它就可以軟鏈到指定版本的 /usr/lib/node/foo/1.2.3/node_modules/bar 。然后,當(dāng) bar 包中的代碼調(diào)用 require('quux') 時(shí),它同樣也可以軟鏈到指定版本的 /usr/lib/node/bar/4.3.2/node_modules/quux 。
模塊加載的全過程(重點(diǎn),下面寫的偽代碼流程一定要記住)
要獲取在調(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
模塊緩存
所有的模塊都會(huì)在第一次加載之后被緩存起來。 這意味著你每次調(diào)用 require('foo') 將得到完全相同的對象。
對 require('foo') 的多次調(diào)用可能并不會(huì)多次執(zhí)行該模塊的代碼。 這是一個(gè)重要的功能。 使用它,可以返回“partially done”對象,從而允許根據(jù)依賴關(guān)系一層一層地加載模塊,即使這樣做可能會(huì)導(dǎo)致循環(huán)依賴。
如果要讓某個(gè)模塊在每次被加載時(shí)都去執(zhí)行代碼,則需要 exports 一個(gè)函數(shù),并調(diào)用該函數(shù)即可。
模塊緩存注意事項(xiàng)
模塊是基于其解析出來的文件名進(jìn)行緩存。根據(jù)調(diào)用模塊的路徑,被調(diào)用的模塊可能會(huì)解析出不同的文件名(從node_modules文件夾加載)。如果解析出來的是不同的文件,它不保證每次 require('foo') 總是返回相同的對象。
另外,在不區(qū)分大小寫的文件系統(tǒng)或操作系統(tǒng)上,不同的解析文件名可以指向相同的文件,但緩存仍將它們視為不同的模塊,并將重新加載該文件多次。 例如, require('./ foo') 和 require('./ FOO') 返回兩個(gè)不同的對象,而不管 ./foo 和 ./FOO 是否是同一個(gè)文件。
核心模塊
Node.js有些模塊被編譯成二進(jìn)制文件。 本文檔中的其他部分將對這些模塊進(jìn)行更詳細(xì)的描述。
核心模塊在Node.js的源碼 lib/ 文件夾中。
如果核心模塊的模塊標(biāo)識(shí)傳遞給 require() ,則它們總是優(yōu)先加載。 例如,即使有一個(gè)自定義模塊叫 http ,我們?nèi)?zhí)行 require('http') 也將始終返回內(nèi)置的 HTTP 模塊,
循環(huán)引用
當(dāng)循環(huán)引用 require() 時(shí),返回模塊可能并沒有執(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 時(shí), a.js 依次加載 b.js . 此時(shí), b.js 嘗試加載 a.js . 為了防止無限循環(huán),將 a.js 導(dǎo)出對象的未完成副本返回到 b.js 模塊。 b.js 然后完成加載,并將其導(dǎo)出對象提供給 a.js 模塊。
當(dāng) app.js 加載了這兩個(gè)模塊時(shí),它們都已經(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將使用一個(gè)函數(shù)包裝器來將模塊內(nèi)容包裹起來,如下所示:
(function (exports, require, module, __filename, __dirname) { // 你的模塊代碼 });
通過這樣做,Node.js實(shí)現(xiàn)了以下幾點(diǎn):
它將模塊內(nèi)部的頂級(jí)變量(定義為 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)識(shí),為該模塊文件在系統(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)識(shí)解析模塊,返回絕對路徑 main: undefined | Object, // 應(yīng)用的主(main)模塊 extensions: {'.js':Function, '.json':Function, '.node':Function}, cache: Object // 模塊緩存,以模塊的絕對路徑為key }