• home > webfront > ECMAS > npm-node >

    再談Node.js的模塊加載方式+機制與運行原理

    Author:zhizunbao Date:

    模塊的加載require( __ )||import __ 這里應該怎么填?為什么require( express )可以直接加載?他的加載順序是什么?加載原理是什么?

    此篇主要由以下幾篇神文凝練而成

    Node.js的模塊載入方式與機制

    模塊以及加載機制,主要討論找不到模塊的問題

    結合源碼分析 Node.js 模塊加載與運行原理

    npm 模塊加載機制詳解


    相對路徑指定模塊,一般用于加載自己的模塊。

        必須用到的符號:

             ./ 表示當前目錄,相對路徑所相對的就是當前的目錄

             ../ 表示上一級模塊,可以無限使用直到跳轉到根目錄

    nodejs的模塊分類

    • 核心模塊:包含在 Node.js 源碼中,被編譯進 Node.js 可執行二進制文件 JavaScript 模塊,也叫 native 模塊,比如常用的 http, fs 等等

    • C/C++ 模塊,也叫 built-in 模塊,一般我們不直接調用,而是在 native module 中調用,然后我們再 require

    • native原生模塊:http  fs path等,這些模塊都在源碼包的lib目錄下面,nodejs安裝好之后是找不到這些模塊的,都作為node.exe的一部分了,require這些模塊永遠沒問題的,如果哪天出現問題了,直接重啟電腦或者重裝node。有什么疑問可以通過下載源碼對這些原生模塊的功能進行查看。地址:https://nodejs.org/download/。

    • 第三方模塊-文件模塊:非 Node.js 源碼自帶的模塊都可以統稱第三方模塊,比如 express,webpack 等等。

      JavaScript 模塊,這是最常見的,我們開發的時候一般都寫的是 JavaScript 模塊

      JSON 模塊,這個很簡單,就是一個 JSON 文件

      C/C++ 擴展模塊,使用 C/C++ 編寫,編譯之后后綴名為 .node

    • 自定義模塊:我們自己寫的模塊,之所以獨立出來是因為其加載和另兩種模塊有區別。

    怎樣定義模塊

    nodejs聲明一個模塊有2中做法

        exports.module_name

        module.exports

    關于這兩個的區別也很簡單,不過要講明白很費勁,關鍵點在于知道有 module 這個全局變量的存在,打印出來并做幾次嘗試,就完全明白了,這里有一篇非常精彩  的關于這兩者異同的文章:《nodejs中exports與module.exports的實踐

    第三方模塊安裝在哪(NPM)

    幾條命令

    npm config get/set prefix //查看設置全局安裝目錄,全局安裝的模塊就安裝該目錄下面的node_modules目錄下
    npm install [-g]  // -g 全局安裝,模塊將會安裝到全局目錄下。不帶 -g 則直接安裝在當前所在目錄下,即為本地安裝

     模塊的存在形式

      1、文件包含,這個比較直觀,直接指定到文件名(去掉 .js 后綴),就可以得到文件里面所有導出的模塊。

      2、文件夾包含,通過npm安裝的第三方模塊都是這種方式,指定到模塊所在的文件夾,該文件夾就是模塊名,以express為例:


    express文件結構

    模塊加載機制:

    首先搜索當前目錄下的 package.json 文件,查找里面的mian屬性,如果存在,則加載該屬性所指定的的文件。如果不存在 package.json 或者該文件里面沒有main字段,nodejs將試圖加載 index.js 

    都不存在那么就只有說一聲Cannot find module了。


    node模塊的載入及緩存機制

    1. 載入內置模塊(A Core Module)

    2. 載入文件模塊(A File Module)

    3. 載入文件目錄模塊(A Folder Module)

    4. 載入node_modules里的模塊

    5. 自動緩存已載入模塊

     

    一、載入內置模塊

    Node的內置模塊被編譯為二進制形式,引用時直接使用名字而非文件路徑。當第三方的模塊和內置模塊同名時,內置模塊將覆蓋第三方同名模塊。因此命名時需要注意不要和內置模塊同名。如獲取一個http模塊

        var http = require('http')

    返回的http即是實現了HTTP功能Node的內置模塊。

    二、載入文件模塊

    • 絕對路徑的:var myMod = require('/home/base/my_mod')

    • 相對路徑的:var myMod = require('./my_mod')

    注意,這里忽略了擴展名“.js”,以下是對等的

        var myMod = require('./my_mod')

        var myMod = require('./my_mod.js')


    三、載入文件目錄模塊

    可以直接require一個目錄,假設有一個目錄名為folder,如

        var myMod = require('./folder')

    此時,Node將搜索整個folder目錄,Node會假設folder為一個包并試圖找到包定義文件package.json。如果folder目錄里沒有包含package.json文件,Node會假設默認主文件為index.js,即會加載index.js。如果index.js也不存在,那么加載將失敗。

    +folder

        index.js

        modA.js

        package.json

    +init.js

    package.json定義如下

    {
        "name": "pack",
        "main": "modA.js"
    }

    此時 require('./folder') 將返回模塊modA.js。如果package.json不存在,那么將返回模塊index.js。如果index.js也不存在,那么將發生載入異常。

    四、載入node_modules里的模塊

    如果模塊名不是路徑,也不是內置模塊,Node將試圖去當前目錄的node_modules文件夾里搜索。如果當前目錄的node_modules里沒有找到,Node會從父目錄的node_modules里搜索,這樣遞歸下去直到根目錄。

    不必擔心,npm命令可讓我們很方便的去安裝,卸載,更新node_modules目錄。

    五、自動緩存已載入模塊

    對于已加載的模塊Node會緩存下來,而不必每次都重新搜索。下面是一個示例

    modA.js

    console.log('模塊modA開始加載...')
    exports = function() {
        console.log('Hi')
    }
    console.log('模塊modA加載完畢')

    init.js

    var mod1 = require('./modA')
    var mod2 = require('./modA')
    console.log(mod1 === mod2)

    雖然require了兩次,但modA.js仍然只執行了一次。mod1和mod2是相同的,即兩個引用都指向了同一個模塊對象。

    模塊在每一次加載之后都會被緩存起來。這也就意味著在每一次使用require(‘foo’)時,返回的都是同一個對象,即使文件隨后被修改過。


    多次執行require('./modA')并不會導致模塊代碼被執行多次,這是一個很重要的功能。

    而如果你確實是想讓一個模塊的代碼被執行多次,那么就導出一個函數,然后多次調用那個函數。

    模塊緩存注意事項

    模塊緩存基于它們的resolved filename。由于被調用的模塊的位置[從node_modules文件夾中加載]不同,所以模塊可能會被解析為不同的文件名。

    如果解析的結果是不同的文件,但卻總是返回相同的對象,這并不是我們所希望的結果。

    此外,在大小寫不敏感的文件系統或操作系統上,不同的文件名可能會指向相同的文件,但緩存系統依舊會將它們視為不同的模塊,并多次加載文件。例如,require(‘./foo’) 和 require(‘./FOO’)不管./foo或./FOO是否為同一個文件,都將會返回兩個不同的對象。

    require('__')||import '__' 這里應該怎么填

    • 相對路徑指定模塊,一般用于加載自己的模塊。必須用到的符號:

          ./ 表示當前目錄,相對路徑所相對的就是當前的目錄

          ../ 表示上一級模塊,可以無限使用直到跳轉到根目錄

    • 絕對路徑指定模塊地址,除了原生模塊之外,任何文件模塊都可以加載到,除非路徑出錯了。

      比如我們可以這樣子加載express模塊 '/www/node_module/express/'

    • 直接使用 require('xxx') 那么所加載的模塊要么是原生模塊,要么該模塊在某個node_modules目錄下面

    混合模式

    為了確定通過require()函數來加載時,調用的明確的文件名,我們可以使用

    require.resolve()函數。

    能將上述的方式搭配利用,需要的就是require.resolve方法體現出來的高水平算法。

    文件的路徑為Y,require(x):

    1. 如果X是核心模塊
      a. 返回核心模塊
      b. 結束代碼

    2. 如果 X 以 ‘./‘ 或 ‘/‘ 或 ‘../‘開始
      a. 以文件的形式加載(Y + X)
      b. 以目錄的形式加載(Y + X)

    3. 以NODE_MODULES的形式來加載(X, dirname(Y))

    4. 拋出異常 “not found”

    以文件的形式加載的具體說明

    1.如果存在文件x,那么就把x以javascript文本的形式來加載
    1.如果存在文件x.js,那么就把x.js以javascript文本的形式來加載
    3.如果存在文件x.json,  那么就解析 x.json 為一個 JavaScript 對象
    4.如果存在文件x.node, 那么就把 x.node 作為一個二進制文件來加載

    以目錄的形式加載的具體說明

    1. 如果存在文件 X/package.json,
      a. 解析 X/package.json, 然后尋在main字段.
      b. let M = X + (json 的main字段)
      c. 以文件的形式加載(M)

    2. 如果存在文件X/index.js, 把 X/index.js 以javascript文本的形式來加載

    3. 如果存在文件X/index.json, 解析 X/index.json 為一個 JavaScript 對象

    4. 如果存在文件X/index.node, 把 X/index.node 作為一個二進制文件來加載

    以NODE_MODULES的形式來加載(X, START)的具體說明

    1. let DIRS=NODE_MODULES_PATHS(START)

    2. for each DIR in DIRS:
      a. 以文件的形式加載(DIR/X)
      b. 以目錄的形式加載(DIR/X)

    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
      c. DIR = path join(PARTS[0 .. I] + “node_modules”)
      b.    DIRS = DIRS + DIR
      c. let I = I - 1

    5. return DIRS

    nodejs模塊運行分析

    假設有一個 index.js 文件,里面只有一行很簡單的 console.log('hello world') 代碼。當輸入 node index.js 的時候,Node.js 是如何編譯、運行這個文件的呢?

    從《結合源碼分析 Node.js 模塊加載與運行原理》,

    可以看到,主要是調用 Module._load 來加載執行 process.argv[1]。

    下文我們在分析模塊的 require 的時候,也會來到 lib/module.js 中,也會分析到 Module._load。因此我們可以看出,Node.js 啟動一個文件的過程,其實到最后,也是 require 一個文件的過程,可以理解為是立即 require 一個文件

    分析 require 的原理

    var http = require('http');

    那么當執行這一句代碼的時候,會發生什么呢?

    • 先生成 cacheKey,判斷相應 cache 是否存在,若存在直接返回

    • 如果 path 的最后一個字符不是 /

      • 判斷路徑如果存在,直接返回

      • 嘗試在路徑后面加上 .js, .json, .node 三種后綴名,判斷是否存在,存在則返回

      • 嘗試在路徑后面依次加上 index.js, index.json, index.node,判斷是否存在,存在則返回

      • 如果路徑是一個文件并且存在,那么直接返回文件的路徑

      • 如果路徑是一個目錄,調用 tryPackage 函數去解析目錄下的 package.json,然后取出其中的 main 字段所寫入的文件路徑

      • 如果還不成功,直接對當前路徑加上 .js, .json, .node 后綴名進行嘗試

    • 如果 path 的最后一個字符是 /

      • 調用 tryPackage ,解析流程和上面的情況類似

      • 如果不成功,嘗試在路徑后面依次加上 index.js, index.json, index.node,判斷是否存在,存在則返回

    process1

    這里目前還沒有如何跟直白簡練的文字來概括此文內容,所以,及不copy了


    轉載本站文章《再談Node.js的模塊加載方式+機制與運行原理》,
    請注明出處:http://www.qsexmk.tw/html/webfront/ECMAScript/nodejs/121.html

    彩票快乐双彩