Node.js常用模組Module的載入機制與使用

五月君發表於2019-04-08

Node.js模組機制採用了Commonjs規範,彌補了當前JavaScript開發大型沒有標準的缺陷,類似於Java中的類檔案,Python中的import機制,NodeJs中可以通過module.exports、require來匯出和引入一個模組.

在模組載入機制中,NodeJs採用了延遲載入的策略,只有在用到的情況下,系統模組才會被載入,載入完成後會放到binding_cache中。

推薦技術部落格: Node.js技術棧

快速導航

面試指南

  • require的載入機制?,參考:模組載入機制
  • module.exports與exports的區別,參考:module.exports與exports的區別
  • 假設有a.js、b.js兩個模組相互引用,會有什麼問題?是否為陷入死迴圈?,參考:#
  • a模組中的undeclaredVariable變數在b.js中是否會被列印?,參考:#

模組的分類

系統模組

  • C/C++模組,也叫built-in內建模組,一般用於native模組呼叫,在require出去

  • native模組,在開發中使用的Nodejs的http、buffer、fs等,底層也是呼叫的內建模組(C/C++)。

第三方模組

這裡非Nodejs自帶的模組稱為第三方模組,其實還分為路徑形式的檔案模組(以.../開頭的)和自定義的模組(比如express、koa框架、moment.js等)

  • javaScript模組:例如hello.js

  • json模組:例如hello.json

  • C/C++模組:編譯之後副檔名為.node的模組,例如hello.node

目錄結構

├── benchmark                         一些nodejs效能測試程式碼
├── deps                              nodejs依賴
├── doc                               文件
├── lib                               nodejs對外暴露的js模組原始碼
├── src                               nodejs的c/c++原始碼檔案,內建模組
├── test                              單元測試
├── tools                             編譯時用到的工具
├── doc                               api文件
├── vcbuild.bat                       win平臺makefile檔案
├── node.gyp                          node-gyp構建編譯任務的配置檔案                           
...

模組載入機制

面試中可能會問到能說下require的載入機制嗎?

在Nodejs中模組載入一般會經歷3個步驟,路徑分析檔案定位編譯執行

按照模組的分類,按照以下順序進行優先載入:

  • 系統快取:模組被執行之後會會進行快取,首先是先進行快取載入,判斷快取中是否有值。

  • 系統模組:也就是原生模組,這個優先順序僅次於快取載入,部分核心模組已經被編譯成二進位制,省略了路徑分析檔案定位,直接載入到了記憶體中,系統模組定義在Node.js原始碼的lib目錄下,可以去檢視。

  • 檔案模組:優先載入.../開頭的,如果檔案沒有加上副檔名,會依次按照.js.json.node進行副檔名補足嘗試,那麼在嘗試的過程中也是以同步阻塞模式來判斷檔案是否存在,從效能優化的角度來看待,.json.node最好還是加上檔案的副檔名。

  • 目錄做為模組:這種情況發生在檔案模組載入過程中,也沒有找到,但是發現是一個目錄的情況,這個時候會將這個目錄當作一個來處理,Node這塊採用了Commonjs規範,先會在專案根目錄查詢package.json檔案,取出檔案中定義的main屬性("main": "lib/hello.js")描述的入口檔案進行載入,也沒載入到,則會丟擲預設錯誤: Error: Cannot find module 'lib/hello.js'

  • node_modules目錄載入:對於系統模組、路徑檔案模組都找不到,Node.js會從當前模組的父目錄進行查詢,直到系統的根目錄

圖片描述

require模組載入時序圖

模組迴圈引用

問題1

假設有a.js、b.js兩個模組相互引用,會有什麼問題?是否為陷入死迴圈?看以下例子:

a.js

console.log('a模組start');

exports.test = 1;

undeclaredVariable = 'a模組未宣告變數'

const b = require('./b');

console.log('a模組載入完畢: b.test值:',b.test);
複製程式碼

b.js

console.log('b模組start');

exports.test = 2;

const a = require('./a');

console.log('undeclaredVariable: ', undeclaredVariable);

console.log('b模組載入完畢: a.test值:', a.test);
複製程式碼

問題2

問題2: a模組中的undeclaredVariable變數在b.js中是否會被列印?

控制檯執行node a.js,檢視輸出結果:

a模組start
b模組start
undeclaredVariable:  a模組未宣告變數
b模組載入完畢: a.test值: 1
a模組載入完畢: b.test值: 2
複製程式碼

問題1,啟動a.js的時候,會載入b.js,那麼在b.js中又載入了a.js,但是此時a.js模組還沒有執行完,返回的是一個a.js模組的exports物件未完成的副本給到b.js模組。然後b.js完成載入之後將exports物件提供給了a.js模組

問題2,因為undeclaredVariable是一個未宣告的變數,也就是一個掛在全域性的變數,那麼在其他地方當然是可以拿到的。

在執行程式碼之前,Node.js會使用一個程式碼封裝器進行封裝,例如下面所示:

(function(exports, require, module, __filename, __dirname) {
// 模組的程式碼
});
複製程式碼

exports與moduleexports的區別

exports與module.exports的區別

exports相當於module.exports 的快捷方式如下所示:

const exports = modules.exports;
複製程式碼

但是要注意不能改變exports的指向,我們可以通過 exports.test = 'a' 這樣來匯出一個物件, 但是不能向下面示例直接賦值,這樣會改變exports的指向

//錯誤的寫法 將會得到undefined
exports = {
  'a': 1,
  'b': 2
}

//正確的寫法
modules.exports = {
  'a': 1,
  'b': 2
}

複製程式碼

更好的理解之間的關係,可以參考JavaScript中的物件引用

作者:五月君
連結:www.imooc.com/article/284…
來源:慕課網
Github: Node.js技術棧

相關文章