博客 / 詳情

返回

詳解node中引入模塊的原理

  • 1. 模塊機制

    • 1.1 commonjs規範
    • 1.2 node的模塊實現(node中引入模塊的過程)

      • 1.2.1 優先從緩存中加載
      • 1.2.2 路徑分析
      • 1.2.3 文件定位
      • 1.2.4 模塊編譯
    • 1.3 核心模塊

      • 1.3.1 js核心模塊的編譯過程
      • 1.3.2 c/c++核心模塊的編譯過程
      • 1.3.3 核心模塊的引入流程
    • 1.4 c/c++擴展模塊

1. 模塊機制

1.1 commonjs規範

  1. CommonJS規範為JavaScript制定了一個美好的願景——希望JavaScript能夠在任何地方運行。
  2. CommonJS對模塊的定義十分簡單,主要分為模塊引用、模塊定義和模塊標識3個部分。
  • 模塊引用:通過require方法引入模塊
var math = require('math');
  • 模塊定義:通過exports對象導出當前模塊的方法或變量(exports是module的屬性)
exports.add = function() {}
  • 模塊標識:就是傳遞給require方法的參數,可以是以下幾種形式(可以沒有文件後綴名.js):

    • 符合小駝峯命名的字符串
    • 以.、..開頭的相對路徑
    • 絕對路徑

1.2 node的模塊實現(node中引入模塊的過程)

node在實現中並非完全按照connonjs規範,而是對模塊規範進行了一定的取捨,同時增加了少許自身的特性。

1. 在node中引入模塊需要經歷如下3個步驟:

  • 路徑分析
  • 文件定位
  • 編譯執行

2. node中,模塊分為以下兩類:

  • 核心模塊:node提供的模塊

    • 核心模塊在node源代碼編譯過程中,編譯進了二進制執行文件
    • 在node進程啓動時,部分核心模塊就被直接加載到了內存中(所以這部分核心模塊在引入時,文件定位和編譯執行這兩部分是可以省略的,且在路徑分析中優先判斷,加載速度最快
  • 文件模塊:用户編寫的模塊

    • 需要在運行時動態加載,需要完整的路徑分析、文件定位和編譯執行。速度較慢

以下為詳細的模塊加載過程:

1.2.1 優先從緩存中加載

  • node對引入過的模塊都會進行緩存,以減少二次引入時的開銷(和瀏覽器緩存的不同之處在於:瀏覽器僅僅緩存文件,而node緩存的是編譯和執行後的對象
  • 對核心模塊的緩存檢查優先於文件模塊

1.2.2 路徑分析

因為標識符有幾種形式,所以對於不同的標識符,模塊的查找和定位有着不同程度的差異。模塊分類如下:

  • 核心模塊:如http、fs、path等
  • 以.或..開始的相對路徑文件模塊
  • 以/開始的絕對路徑文件模塊
  • 非路徑形式的文件模塊,如自定義的文件模塊

1. 核心模塊
核心模塊的優先級僅次於緩存加載,它在node源代碼編譯過程中被編譯成了二進制文件,加載速度最快。

2. 路徑形式的文件模塊
require會將路徑轉化為真實路徑,並以真實路徑作為索引,將編譯執行後的結果放到緩存中,以使二次加載更快。

3. 自定義模塊
node會逐個嘗試模塊路徑中的路徑,直到找到目標文件為止。

  • 模塊路徑概念:模塊路徑是node在定位文件模塊的具體文件時指定的查找策略,具體表現為一個路徑組成的數組(可通過module.paths輸出)
  • 模塊路徑的生成規則:

    • 當前文件目錄下的node_modules目錄
    • 父目錄下的node_modules目錄
    • 父目錄的父目錄下的node_modules目錄
    • 沿路徑向上逐級遞歸,直到根目錄下的node_modules目錄

1.2.3 文件定位

  • 文件擴展名的分析
  • 目錄和包的處理

1. 文件擴展名的分析:當傳遞給require的標識符不包括文件擴展名時,node會按照以下次序補足擴展名,依次嘗試:

  • .js
  • .json
  • .node

在嘗試的過程中,node會調用fs模塊同步阻塞式的判斷文件是否存在。因為node是單線程的,所以這裏是一個會引發性能問題的地方。(解決:在引入.json和.node文件時,加上擴展名)

2. 目錄和包的處理:require通過分析文件擴展名後,可能得到的是一個目錄,此時node會將目錄當做一個包來處理

  • 首先,node在當前目錄下查找package.json。通過json.parse解析出包描述對象,從中取出main屬性指定的文件名進行分析,若缺少擴展名,則會進入擴展名分析的步驟。
  • 若main指定的文件名錯誤,或者沒有package.json,那麼node會將index作為文件名。然後依次查找index.js、index.json、index.node
  • 如果在目錄分析的過程中沒有成功定位到任何文件,則自定義模塊會進入到下一個模塊路徑繼續查找。若遍歷完模塊路徑後仍然沒有,則會拋出查找失敗的異常。

1.2.4 模塊編譯

定位到具體的文件後,node會新建一個模塊對象,然後根據路徑載入和編譯。對於不同的擴展名,其載入方式如下:

  • .js文件:通過fs模塊同步讀取文件後編譯執行
  • .node文件:這是用c/c++編寫的擴展文件,通過dlopen加載最後編譯生成的文件
  • .json文件:通過fs模塊同步讀取文件後,用Json.parse解析返回結果
  • 其餘擴展名文件:都被當做.js文件載入

注:每個編譯成功的模塊,都會將其文件路徑作為索引緩存在Module._cache對象上,以提高二次引入的性能

1. js模塊的編譯
node對獲取的js文件進行了頭尾的包裝,包裝後的代碼會通過vm原生模塊的runInThisContext()執行

(function(exports, requiure, module, __filename, __dirname){
    ...
}

2. c/c++模塊的編譯
node調用process.dlopen()進行加載和執行,(dlope在windows和*nux平台通過libuv進行了兼容).node模塊並不需要編譯,因為他是編寫c/c++模塊之後編譯生成的

3. json文件的編譯

  • 通過fs模塊同步讀取到json文件的內容
  • 通過JSON.parse()得到對象
  • 複製給module.exports

1.3 核心模塊

  • c/c++編寫(存放在node的src目錄)
  • js編寫(存放在lib目錄)

1.3.1 js核心模塊的編譯過程

  • 轉存為c/c++代碼:node通過v8附帶的js2c.py工具,將所有內置的js代碼轉換成c++裏的數組,生成node_natives.h頭文件。(在這個過程中,js代碼以字符串的形式存儲在node命名空間中。在啓動node進程時,js代碼直接加載進內存中
  • 編譯js核心模塊:也經歷了頭尾的包裝過程(與文件模塊的區別在於:獲取源碼的方式和緩存執行結果的位置)

    • 核心模塊從內存中加載
    • 編譯成功的模塊緩存到NativeModule._cache對象; 文件模塊則緩存到Module._cache對象

1.3.2 c/c++核心模塊的編譯過程

  • 內建模塊:全部由c/c++編寫(如buuffer、fs、os等)
  • c/c++完成核心部分,js實現封裝

1. 內建模塊

  • 每一個內建模塊在定義之後,都通過NODE_MODULE宏將模塊定義到node命名空間中。
  • nodex_extensions.h頭文件將這些散列的內建模塊統一放到了node_module_list數組
  • node提供了get_builtin_module()從node_module_list中取出內建模塊
  • 內建模塊的導出:通過process.binding()來加載內建模塊(process是node在啓動時生成的全局變量)

1.3.3 核心模塊的引入流程

  • NODE_MODULE(node_os, reg_func)
  • get_builtin_module('node_os')
  • process.binding('os')
  • NativeModule.require('os')
  • require('os')

1.4 c/c++擴展模塊

  • 模塊編寫:

普通的擴展模塊和內建模塊的區別在於:無需將源代碼編譯進node,而是通過dlopen()動態加載

  • 模塊編譯:通過gpy工具
  • 模塊加載:通過require()加載

    • 對於.node文件使用process.dlopen()加載

      • 通過uv_dlopen()打開動態鏈接庫
      • 通過uv_dlsym()找到動態鏈接庫中通過NODE_MODULE宏定義的方法地址

      注:以上兩個方法都是在libuv庫中實現的。在不同的操作系統下分別調用不同的方法來分別加載.node在該操作系統下對應的文件.so和.dll

user avatar xiangjiaochihuanggua 頭像 guizimo 頭像 yaofly 頭像 chongdianqishi 頭像 codepencil 頭像 columsys 頭像 buxia97 頭像 yilezhiming 頭像 frontoldman 頭像 huanjinliu 頭像 musicfe 頭像 nihaojob 頭像
23 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.