在編寫稍大些的專案的時候,模組化和元件化是當前 JS 的最佳解決方案。在NodeJS中,一般將程式碼合理拆分到不同的JS檔案中,每一個檔案就是一個模組,而檔案路徑就是模組名。
模組化程式碼在 nodejs 中有以下特性:
- Node.js是通過模組的形式進行組織與呼叫的,在編寫每個模組時,都有
require
、exports
、module
三個預先定義好的變數可供使用。 - 所以系統自帶了很多模組
- 同時也提供了新模組的擴充套件機制
1.1 require
require函式用於在當前模組中載入和使用別的模組,傳入一個模組名
(路徑),反回一個模組匯出物件。模組名可使用相對路徑(以./開頭),或者是絕對路徑(以/或C:之類的碟符開頭)。另外,模組名中的.js副檔名可以省略。以下是一個例子。
var foo1 = require('./foo');
var foo2 = require('./foo.js');
var foo3 = require('/home/user/foo');
var foo4 = require('/home/user/foo.js');
// foo1至foo4中儲存的是同一個模組的匯出物件。
複製程式碼
另外,可以使用以下方式載入和使用一個JSON檔案。
var data = require('./data.json');
複製程式碼
1.2 exports
exports物件是當前模組的匯出物件,用於匯出模組公有方法和屬性。別的模組通過require函式使用當前模組時得到的就是當前模組的exports物件。以下例子中匯出了一個公有方法。
exports.hello = function () {
console.log('Hello World!');
};
複製程式碼
1.3 module
通過module物件可以訪問到當前模組的一些相關資訊,但最多的用途是替換當前模組的匯出物件。例如模組匯出物件預設是一個普通物件,如果想改成一個函式的話,可以使用以下方式。
module.exports = function () {
console.log('Hello World!');
};
複製程式碼
- 以上程式碼中,模組預設匯出物件被替換為一個函式。
- 通過命令列引數傳遞給NodeJS以啟動程式的模組被稱為主模組。主模組負責排程組成整個程式的其它模組完成工作。
※ 官方實現require
的方式:
function require(...) {
var module = { exports: {} };
((module, exports) => {
// Your module code here. In this example, define a function.
function some_func() {};
exports = some_func;
// At this point, exports is no longer a shortcut to module.exports, and
// this module will still export an empty default object.
module.exports = some_func;
// At this point, the module will now export some_func, instead of the
// default object.
})(module, module.exports);
return module.exports;
}
複製程式碼
如果 a.js require 了 b.js, 那麼在 b 中定義全域性變數 t = 111 能否在 a 中直接列印出來?
① 每個 .js 能獨立一個環境只是因為 node 幫你在外層包了一圈自執行, 所以你使用 t = 111 定義全域性變數在其他地方當然能拿到. 情況如下:
// b.js
(function (exports, require, module, __filename, __dirname) {
t = 111;
})();
// a.js
(function (exports, require, module, __filename, __dirname) {
// ...
console.log(t); // 111
})();
複製程式碼
a.js 和 b.js 兩個檔案互相 require 是否會死迴圈? 雙方是否能匯出變數? 如何從設計上避免這種問題?
② 不會, 先執行的匯出空物件, 通過匯出工廠函式讓對方從函式去拿比較好避免. 模組在匯出的只是 var module = { exports: {} };
中的 exports, 以從 a.js 啟動為例, a.js 還沒執行完 exports 就是 {}
在 b.js 的開頭拿到的就是 {}
而已.
另外還有非常基礎和常見的問題, 比如 module.exports 和 exports 的區別這裡也能一併解決了 exports 只是 module.exports 的一個引用. 沒看懂可以在細看我以前發的帖子.
再晉級一點, 眾所周知, node 的模組機制是基於 CommonJS 規範的. 對於從前端轉 node 的同學, 如果面試官想問的難一點會考驗關於 CommonJS 的一些問題. 比如比較 AMD, CMD, CommonJS 三者的區別, 包括詢問關於 node 中 require 的實現原理等.
1.4 module.exports 與 exports
- 預設
exports
與module.exports
指向相同的物件的引用,並且此物件是一個空物件{}
。 - 對他們新增屬性,不會破壞他們的一致性
console.log(module.exports === exports); // true
console.log(exports.a); // undefined
// 修改exports
module.exports.a = 100;
console.log(module.exports === exports); // true
console.log(exports); // { a: 100 }
// 修改exports
exports.b = {
a: 100
};
console.log(module.exports === exports); // true
console.log(exports); // { a: 100, b: { a: 100 } }
複製程式碼
- 對他們直接使用賦值號,則會破壞他們的引用關係
console.log(module.exports === exports); // true
module.exports = {c:100};
console.log(exports); // {}
console.log(module.exports); // {c:100}
console.log(module.exports === exports); // false
// 直接修改exports
console.log(module.exports === exports); // false
exports = {
c:100
};
console.log(exports); // {c:100}
console.log(module.exports); // {}
console.log(module.exports === exports); // false
複製程式碼
- 匯出以module.exports為準
1.4 系統自帶模組
可以通過
process.moduleLoadList
列印的 NativeModule 可以檢視到相關的模組資訊。主要系統包括:
在 V8.9.3中,主要的系統模組包括:
[ 'assert', 'buffer', 'console', 'dns', 'domain', 'events', 'fs', 'module', 'net', 'os', 'path', 'querystring', 'readline', 'repl', 'stream', 'string_decoder', 'timers', 'tty', 'url', 'util', 'vm' ]
其中最能代表node.js的最初思想的是net, 'events'這兩個模組。
1.5 系統 API
|穩定性:2 -- 穩定的
|v8.1.1
|
1.5.1 exports 和 module.exports
先看一個例子:
// module circle.js
const { PI } = Math;
exports.area = (r) => PI * r ** 2;
exports.circumference = (r) => 2 * PI * r;
複製程式碼
circle.js
檔案匯出了area()
和circumference()
兩個函式。- 通過在特殊的 exports 物件上指定額外的屬性,函式和物件可以被新增到模組的根部。
- 模組內的本地變數是私有的,因為模組被 Node.js 包裝在一個函式中。 (
PI
是私有的)
第二個例子:
// square.js
module.exports = class Square {
constructor(width) {
this.width = width;
}
area() {
return this.width ** 2;
}
};
// use square module
const Square = require('./square.js');
const mySquare = new Square(2);
console.log(`mySquare 的面積是 ${mySquare.area()}`);
複製程式碼
module.exports
屬性可以被賦予一個新的值(例如函式或物件)。
1.5.2 主模組
-
當 Node.js 直接執行一個檔案時,
require.main
會被設為它的 module。 這意味著可以通過 require.main === module 來判斷一個檔案是否被直接執行: -
module
提供了一個 filename 屬性(通常等同於 __filename),所以可以通過檢查 require.main.filename 來獲取當前應用程式的入口點。
1.5.3 包管理器的技巧 http://nodejs.cn/api/modules.html#modules_addenda_package_manager_tips
1.5.4 快取
-
模組在第一次載入後會被快取。 這也意味著(類似其他快取機制)如果每次呼叫 require('foo') 都解析到同一檔案,則返回相同的物件。
-
多次呼叫 require(foo) 不會導致模組的程式碼被執行多次。 這是一個重要的特性。 藉助它, 可以返回“部分完成”的物件,從而允許載入依賴的依賴, 即使它們會導致迴圈依賴。
-
如果想要多次執行一個模組,可以匯出一個函式,然後呼叫該函式。
模組快取的注意事項:
-
模組是基於其解析的檔名進行快取的。 由於呼叫模組的位置的不同,模組可能被解析成不同的檔名(比如從 node_modules 目錄載入),這樣就不能保證 require('foo') 總能返回完全相同的物件。
-
此外,在不區分大小寫的檔案系統或作業系統中,被解析成不同的檔名可以指向同一檔案,但快取仍然會將它們視為不同的模組,並多次重新載入。 例如,require('./foo') 和 require('./FOO') 返回兩個不同的物件,而不會管 ./foo 和 ./FOO 是否是相同的檔案。
-
建議: 檔名稱按照一定規範進行編排,無大小寫轉換後相同檔名。
1.5.6 核心模組
- 核心模組是 Nodejs將其編譯成二進位制的模組,便於更快速載入
- 核心模組存放在 Node.js 原始碼的 lib/ 目錄下。
- require() 總是會優先載入核心模組。 例如,require('http') 始終返回內建的 HTTP 模組,即使有同名檔案。
1.5.7 迴圈
在模組之間的互相載入時,當 main.js
載入 a.js
時,a.js
又載入 b.js
。 此時,b.js
會嘗試去載入 a.js
, 這樣就造成了無限迴圈。
會返 a.js
的 exports
物件的未完成的副本給 b.js
模組。 然後 b.js
完成載入,並將 exports
物件提供給 a.js
模組。
DEMO:
//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 結束');
//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 結束');
//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);
複製程式碼
分析過程:
- 關鍵詞: 會返 a.js 的 exports 物件的未完成的副本 給 b.js 。
- 未完成程度 : 在出現迴圈引用的時候 返回副本。
- 目的: 不阻礙主流程。
- 執行順序:1 -> 2 -> 5~7 -> 8~16 -> 3 -> 4
- 1~2 : 主流程
- 5~7:按照
require(a.js)
順序執行, - 8~9:按照 b.js 數序執行,
- 10: 出現迴圈:製造 當前狀態下 a.js 副本,狀態保留至副本創造時。
- 11~16: 順序執行,然後
b.js
完成載入,並將exports
物件提供給a.js
模組。 - 3: 快取讀取 b.js 但已經在 a.js 中執行過,這裡不執行。
- 4: main.js 執行完畢。
- 輸出結果:
$ node main.js
main 開始
a 開始
b 開始
在 b 中,a.done = false
b 結束
在 a 中,b.done = true
a 結束
在 main 中,a.done=true,b.done=true
複製程式碼
- 需要仔細的規劃, 以允許迴圈模組依賴在應用程式內正常工作。
1.5.8 檔案模組(系統自帶的模組)
-
如果按確切的檔名沒有找到模組,則 Node.js 會嘗試帶上
.js
、.json
或.node
擴充名再載入。.js
檔案會被解析為 JavaScript 文字檔案.json
檔案會被解析為 JSON 文字檔案。.node
檔案會被解析為通過 dlopen 載入的編譯後的外掛模組。
-
以 '/' 為字首的模組是檔案的絕對路徑。 例如,
require('/home/marco/foo.js')
會載入 /home/marco/foo.js 檔案。 -
以 './' 為字首的模組是相對於呼叫 require() 的檔案的。
-
當沒有以
'/'
、'./'
或'../'
開頭來表示檔案時,這個模組必須是一個核心模組或載入自node_modules
目錄。 -
如果給定的路徑不存在,則 require() 會丟擲一個 code 屬性為
'MODULE_NOT_FOUND'
的 Error。
1.5.9 目錄作為模組
如果把程式和依賴庫放在統一個資料夾下,提供一個單一的入口指向它。把目錄傳給 require()
作為一個引數,即為 目錄作為模組 引用。
- 使用
package.json
指定main
入口模組:
{
"name" : "some-library",
"main" : "./lib/some-library.js"
}
複製程式碼
如果這是在 ./some-library 目錄中,則 require('./some-library')
會試圖載入 ./some-library/lib/some-library.js
。
- 無
package.json
指定,則 Nodejs 會試圖載入index.js
或index.node
require('./some-library')
:./some-library/index.js
或./some-library/index.node
1.5.10 node_modules 目錄載入
傳入require()
的路徑不是一個核心模組,Nodejs 從父目錄開始,嘗試從父目錄的node_modules
中載入模組。
如果在'/home/ry/projects/foo.js'
檔案裡呼叫了 require('bar.js')
,則 Node.js 會按以下順序查詢:
- /home/ry/projects/node_modules/bar.js
- /home/ry/node_modules/bar.js
- /home/node_modules/bar.js
- /node_modules/bar.js
通過在模組名後包含一個路徑字尾,可以請求特定的檔案或分散式的子模組。 例如,require('example-module/path/to/file') 會把 path/to/file 解析成相對於 example-module 的位置。 字尾路徑同樣遵循模組的解析語法。 1.5.11 從全域性目錄載入 如果 NODE_PATH 環境變數被設為一個以冒號分割的絕對路徑列表,則當在其他地方找不到模組時 Node.js 會搜尋這些路徑。
**注意:**在 Windows 系統中,NODE_PATH
是以分號間隔的。
在當前的模組解析演算法執行之前,NODE_PATH
最初是建立來支援從不同路徑載入模組的。
雖然 NODE_PATH 仍然被支援,但現在不太需要,因為 Node.js 生態系統已制定了一套存放依賴模組的約定。 有時當人們沒意識到 NODE_PATH 必須被設定時,依賴 NODE_PATH 的部署會出現意料之外的行為。 有時一個模組的依賴會改變,導致在搜尋 NODE_PATH 時載入了不同的版本(甚至不同的模組)。
此外,Node.js 還會搜尋以下位置:
- $HOME/.node_modules
- $HOME/.node_libraries
- $PREFIX/lib/node
其中 $HOME
是使用者的主目錄,$PREFIX
是 Node.js 裡配置的 node_prefix
。
這些主要是歷史原因。
注意:強烈建議將所有的依賴放在本地的 node_modules 目錄。 這樣將會更快地載入,且更可靠。
1.5.12 [Module Scope]
__dirname
<string>
當前模組的資料夾的名字- 等同於:
path.dirname(__filename)
的值
DEMO: 執行 /Users/demo/example.js
console.log(__dirname);
// Prints: /Users/demo
console.log(path.dirname(__filename));
// Prints: /Users/demo
複製程式碼
1.5.12 [Module Scope]
__filename
<string>
當前模組的檔名稱---解析後的絕對路徑。
DEMO: 執行/Users/demo/example.js
console.log(__filename);
// Prints: /Users/demo/example.js
console.log(__dirname);
// Prints: /Users/demo
複製程式碼
給定兩個模組: a 和 b, 其中 b 是 a 的一個依賴。
檔案目錄結構如下:
- /Users/mjr/app/a.js
- /Users/mjr/app/node_modules/b/b.js
使用__filename
===>
- b.js 中對 __filename 的引用將會返回
/Users/mjr/app/node_modules/b/b.js
- a.js 中對 __filename 的引用將會返回
/Users/mjr/app/a.js
1.5.13 [Module Scope]
exports / module
- exports: 這是一個對於 module.exports 的更簡短的引用形式。
- module:對當前模組的引用,
1.5.14 require()
用於引入模組
-
require.cache
: 被引入的模組將被快取在這個物件中。從此物件中刪除鍵值對將會導致下一次 require 重新載入被刪除的模組。注意不能刪除 native addons(原生外掛),因為它們的過載將會導致錯誤。 -
require.resolve(request[, options])
使用內部的 require() 機制查詢模組的位置, 此操作只返回解析後的檔名,不會載入該模組。- request
<string>
需要解析的模組路徑。 - options
<Object>
- paths
<Array>
解析模組的起點路徑。此引數存在時,將使用這些路徑而非預設解析路徑。 注意此陣列中的每一個路徑都被用作模組解析演算法的起點,意味著 node_modules 層級將從這裡開始查詢。
- paths
- Returns:
<string>
- request
-
require.resolve.paths(request])
返回一個陣列,其中包含解析 request 過程中被查詢的路徑。 如果 request 字串指向核心模組(例如 http 或 fs),則返回 null。- request:
<string>
被查詢解析路徑的模組的路徑。 - 返回:
<Array> | <null>
- request:
DEMO:
// modules
> require.resolve.paths('aaa')
[ '/Users/zhengao/repl/node_modules',
'/Users/zhengao/node_modules',
'/Users/node_modules',
'/node_modules',
'/Users/zhengao/.node_modules',
'/Users/zhengao/.node_libraries',
'/Users/zhengao/.nvm/versions/node/v8.9.3/lib/node',
'/Users/zhengao/.node_modules',
'/Users/zhengao/.node_libraries',
'/Users/zhengao/.nvm/versions/node/v8.9.3/lib/node' ]
// 核心模組
> require.resolve.paths('http')
null
複製程式碼
1.5.15 module物件
<Object>
- 在每個模組中,module 的自由變數是一個指向表示當前模組的物件的引用。 為了方便,module.exports 也可以通過全域性模組的 exports 物件訪問。
- module 不是全域性的,而是每個模組本地的。只不過每個模組都有一個 module 物件而已。
> module
Module {
id: '<repl>',
exports: {},
parent: undefined,
filename: null,
loaded: false,
children: [],
paths: [
'/Users/zhengao/repl/node_modules',
'/Users/zhengao/node_modules',
'/Users/node_modules',
'/node_modules',
'/Users/zhengao/.node_modules',
'/Users/zhengao/.node_libraries',
'/Users/zhengao/.nvm/versions/node/v8.9.3/lib/node'
]
}
複製程式碼
module.children
:<Array>
- 被該模組引用的模組物件。
module.exports
:<Object>
- module.exports 物件是由模組系統建立的。許多人希望他們的模組成為某個類的例項。 為了實現這個,需要將期望匯出的物件賦值給 module.exports。
- **注意,**將期望的物件賦值給 exports 會簡單地重新繫結本地 exports 變數,這可能不是期望的。
DEMO:
// 許多人希望他們的模組成為某個類的例項, 需要將期望匯出的物件賦值給 module.exports
//a.js
const EventEmitter = require('events');
module.exports = new EventEmitter();
// 處理一些工作,並在一段時間後從模組自身觸發 'ready' 事件。
setTimeout(() => {
module.exports.emit('ready');
}, 1000);
// 然後,在另一個檔案中可以這麼做:
// b.js
const a = require('./a.js');
a.on('ready', () => {
console.log('模組 a 已準備好');
});
複製程式碼
*DEMO: *
// 注意,對 module.exports 的賦值必須立即完成。 不能在任何回撥中完成。否則無效
// x.js:
setTimeout(() => {
module.exports = { a: 'hello' };
}, 0);
// y.js:
const x = require('./x');
console.log(x.a);
複製程式碼
exports
快捷方式- exports 變數是在模組的檔案級別作用域內有效的,它在模組被執行前被賦予 module.exports 的值。
- 它有一個快捷方式,以便
module.exports.f = ...
可以被更簡潔地寫成exports.f = ...
。 - 注意,就像任何變數,如果一個新的值被賦值給 exports,它就不再繫結到 module.exports:
module.exports.hello = true; // 從對模組的引用中匯出
exports = { hello: false }; // 不匯出,只在模組內有效
//當 module.exports 屬性被一個新的物件完全替代時,也會重新賦值 exports,例如:
module.exports = exports = function Constructor() {
// ... 及其他
};
//為了解釋這個行為,想象對 require() 的假設實現,它跟 require() 的實際實現相當類似:
function require(/* ... */) {
const module = { exports: {} };
((module, exports) => {
// 模組程式碼在這。在這個例子中,定義了一個函式。
function someFunc() {}
exports = someFunc;
// 此時,exports 不再是一個 module.exports 的快捷方式,
// 且這個模組依然匯出一個空的預設物件。
module.exports = someFunc;
// 此時,該模組匯出 someFunc,而不是預設物件。
})(module, module.exports);
return module.exports;
}`
複製程式碼
module.filename
: 模組的完全解析後的檔名。module.id
:<string>
模組的識別符號。 通常是完全解析後的檔名。module.loaded
:<boolean>
模組是否已經載入完成,或正在載入中。module.parent
:<Object 模組物件>
最先引用該模組的模組。module.paths
:<String []>
模組的搜尋路徑。module.require(id)
:id
:<string>
- 返回:
<Object>
已解析的模組的module.exports
- module.require 方法提供了一種類似 require() 從原始模組被呼叫的載入模組的方式。
module.builtinModules
:由Node.js提供的所有模組的名稱列表。可以用來驗證模組是否被第三方模組維護。
1.6 Q&A
Q1: 比較AMD,CMD和 CommonJS 三者區別 A:
背景: 網頁越來越像桌面程式,需要一個團隊分工協作、進度管理、單元測試等等......開發者不得不使用軟體工程的方法,管理網頁的業務邏輯。因為有了模組,我們就可以更方便地使用別人的程式碼,想要什麼功能,就載入什麼模組。
- CommonJS:
CommonJS規範是誕生比較早的。NodeJS就採用了CommonJS。CommonJS 是一種同步的模組化規範,是這樣載入模組:
var clock = require('clock');
clock.start();
複製程式碼
這種寫法適合服務端,因為在伺服器讀取模組都是在本地磁碟,載入速度很快。但是,對於瀏覽器,這卻是一個大問題,因為模組都放在伺服器端,等待時間取決於網速的快慢,可能要等很長時間,瀏覽器處於"假死"狀態。
因此,瀏覽器端的模組,不能採用"同步載入"(synchronous),只能採用"非同步載入"(asynchronous)。
這就是AMD規範誕生的背景。比如上面的例子中clock的呼叫必須等待clock.js
請求成功,載入完畢。
- AMD:
AMD,即 (Asynchronous Module Definition),這種規範是非同步的載入模組,requireJs應用了這一規範。先定義所有依賴,然後在載入完成後的回撥函式中執行:
require([module], callback);
第一個引數[module]
,是一個陣列,裡面的成員就是要載入的模組;第二個引數callback,則是載入成功之後的回撥函式。如果將前面的程式碼改寫成AMD形式,就是下面這樣:
require(['math'], function (math) {
math.add(2, 3);
});
複製程式碼
math.add()與math模組載入不是同步的,瀏覽器不會發生假死。所以很顯然,AMD比較適合瀏覽器環境。
目前,主要有兩個Javascript庫實現了AMD規範:require.js
和 curl.js
。
AMD雖然實現了非同步載入,但是開始就把所有依賴寫出來是不符合書寫的邏輯順序的,能不能像commonJS
那樣用的時候再require,而且還支援非同步載入後再執行呢?
- CMD:
CMD (Common Module Definition), 是seajs
推崇的規範,CMD則是依賴就近,用的時候再require。它寫起來是這樣的:
define(function(require, exports, module) {
var clock = require('clock');
clock.start();
});
複製程式碼
AMD和CMD最大的區別是對依賴模組的執行時機處理不同,而不是載入的時機或者方式不同,二者皆為非同步載入模組。
AMD依賴前置,js可以方便知道依賴模組是誰,立即載入;
而CMD就近依賴,需要使用把模組變為字串解析一遍才知道依賴了那些模組,這也是很多人詬病CMD的一點,犧牲效能來帶來開發的便利性,實際上解析模組用的時間短到可以忽略。
- 總結:
- CommonJS: Nodejs 使用的規範,同步載入。適合後端
- AMD,CMD:非同步載入,require 時機不同,AMD:頭部引用, CMD 隨時引用
- 參考:
Q2:Node.js 中 require()
的實現
A:
Q3: 什麼時候使用 exports ,什麼時候使用 module.exports
A:用一句話來說明就是,
require
方能看到的只有module.exports
這個物件,它是看不到exports
物件的,而我們在編寫模組時用到的exports
物件實際上只是對module.exports
的引用。
var module = {
exports:{
name:"我是module的exports屬性"
}
};
//exports是對module.exports的引用,也就是exports現在指向的記憶體地址和module.exports指向的記憶體地址是一樣的
var exports = module.exports;
console.log(module.exports); // { name: '我是module的exports屬性' }
console.log(exports); // { name: '我是module的exports屬性' }
exports.name = "我想改一下名字";
console.log(module.exports); // { name: '我想改一下名字' }
console.log(exports); // { name: '我想改一下名字' }
//看到沒,引用的結果就是a和b都操作同一記憶體地址下的資料
//這個時候我在某個檔案定義了一個想匯出的模組
var Circle = {
name:"我是一個圓",
func:function(x){
return x*x*3.14;
}
};
exports = Circle; // 看清楚了,Circle這個Object在記憶體中指向了新的地址,所以exports也指向了這個新的地址,和原來的地址沒有半毛錢關係了
console.log(module.exports); // { name: '我想改一下名字' }
console.log(exports); // { name: '我是一個圓', func: [Function] }
複製程式碼
- 直接更改引用:
exports
和module.exports
指向的是同一個引用 - 直接賦值: 賦值給
exports
內部作用域使用,賦值給module.exports
可以被require()
引用。
1.7 模組化的基本要求
- 高內聚
- 低耦合
- 邏輯清晰正確
- 要有輸出
- 高扇入,低扇出
- 減少冗餘(相同型別不要超過三遍)
1.8 模組化的程式碼規範
在 Node.js 中使用 CommonJS 使用模組規範