JavaScript 模組相關

慢城小ZA發表於2018-05-18

在編寫稍大些的專案的時候,模組化和元件化是當前 JS 的最佳解決方案。在NodeJS中,一般將程式碼合理拆分到不同的JS檔案中,每一個檔案就是一個模組,而檔案路徑就是模組名。

模組化程式碼在 nodejs 中有以下特性

  1. Node.js是通過模組的形式進行組織與呼叫的,在編寫每個模組時,都有requireexportsmodule 三個預先定義好的變數可供使用。
  2. 所以系統自帶了很多模組
  3. 同時也提供了新模組的擴充套件機制

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

  1. 預設exportsmodule.exports指向相同的物件的引用,並且此物件是一個空物件{}
    JavaScript 模組相關
  2. 對他們新增屬性,不會破壞他們的一致性
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 } }
複製程式碼
  1. 對他們直接使用賦值號,則會破壞他們的引用關係
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
複製程式碼
  1. 匯出以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, 這樣就造成了無限迴圈。

JavaScript 模組相關
為了防止無限的迴圈,Nodejs 提供了一個解決策略。

會返 a.jsexports 物件的未完成的副本給 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 。
  • 未完成程度 : 在出現迴圈引用的時候 返回副本。
  • 目的: 不阻礙主流程。
    JavaScript 模組相關
  • 執行順序: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.jsindex.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 還會搜尋以下位置:

  1. $HOME/.node_modules
  2. $HOME/.node_libraries
  3. $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 層級將從這裡開始查詢。
    • Returns: <string>
  • require.resolve.paths(request]) 返回一個陣列,其中包含解析 request 過程中被查詢的路徑。 如果 request 字串指向核心模組(例如 http 或 fs),則返回 null。

    • request: <string> 被查詢解析路徑的模組的路徑。
    • 返回: <Array> | <null>

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.jscurl.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的一點,犧牲效能來帶來開發的便利性,實際上解析模組用的時間短到可以忽略。


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] }

複製程式碼
  • 直接更改引用: exportsmodule.exports 指向的是同一個引用
  • 直接賦值: 賦值給exports 內部作用域使用,賦值給 module.exports 可以被require() 引用。

1.7 模組化的基本要求

  • 高內聚
  • 低耦合
  • 邏輯清晰正確
  • 要有輸出
  • 高扇入,低扇出
  • 減少冗餘(相同型別不要超過三遍)

1.8 模組化的程式碼規範

在 Node.js 中使用 CommonJS 使用模組規範

相關文章