溫馨提示×

溫馨提示×

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

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

NodeJS學(xué)習(xí)筆記之Module的簡介

發(fā)布時間:2020-09-05 02:33:29 來源:腳本之家 閱讀:166 作者:張亞濤 欄目:web開發(fā)

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)部的變量一定是私有的,因為模塊被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 。

因為 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ù)即可。

模塊緩存注意事項

模塊是基于其解析出來的文件名進(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
}

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

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

AI