NodeJSのモジュールは一例ですか?

5205 ワード

本文はLazlojuryのare-node-js-modules-singletonsから訳します.本論文は筆者のNodeJS入門と最適実践に属するNodeJS基礎シリーズの文章から、NodeJS入門、NodeJSモジュール導出と解析、NodeJS IOStream、NodeJS HTTPSのいくつかの部分を含む.
筆者は以前、requireを使ってモジュールを導入する時、特に状態のあるモジュールを導入する時、何度も導入しても単一の例の特性を維持しているか、あるいは同じファイルに対して異なる経路で導入する時、一致と認識できるかどうか考えています.この特性を解析します.
NodeJSのモジュールはデフォルトでは一例的な性質ですが、私達がプログラミングする時に考えたように必ず一例であるとは保証できません.NodeJSの公式文書によると、あるモジュールの導入は一例として以下の二つの要素の影響を受けますか?
  • Nodeモジュールのキャッシュメカニズムは、例えば、require('/foo')require('/FOO')とでは、2つの異なるオブジェクトを返しますが、FOOと同じファイルです.
  • モジュールは、解析されたファイル名に基づいてキャッシュされており、異なるモジュールが呼び出された経路に依存してキャッシュされて識別されるため、require('foo')を使用すると、いつまでも同じオブジェクトに戻るとは保証されず、異なるファイルパスによって異なるオブジェクトが得られる可能性がある.
  • 新しいNodeJSモジュールを作成します.
    NodeJS文書によると、ファイルとモジュールは一対一の関係です.これも上述したモジュールキャッシュ機構の基礎を説明するものであり、まず簡単なモジュールを作成する.
    // counter.js 
    
    let value = 0
    
    module.exports = {
      increment: () => value++,
      get: () => value,
    }
    counter.jsで私達はある私有変数を作成しました.また、共通のincrementとget方法でしか操作できません.このモジュールは応用において次のような方法で使用できます.
    // app.js
    const counter = require(‘./counter.js’)
    
    counter.increment()
    counter.increment()
    
    console.log(counter.get()) // prints 2
    console.log(counter.value) // prints undefined as value is private
    Module Caching
    NodeJSはモジュールを初めて導入してからキャッシュします.公式文書では次のように説明されています.
    Every call to require(‘foo’)will get exactly the same object returned,if it would reolve to the same file.
    私達も以下の簡単な例を通してこの言葉を検証することができます.
    // app-singleton.js
    
    const counter1 = require(‘./counter.js’)
    const counter2 = require(‘./counter.js’)
    
    counter1.increment()
    counter1.increment()
    counter2.increment()
    
    console.log(counter1.get()) // prints 3
    console.log(counter2.get()) // also prints 3
    このモジュールを二回導入したにもかかわらず、同じ対象を指していることが分かります.でも、同じモジュールを導入するたびに、同じ対象を得るわけではありません.NodeJSでは、モジュールオブジェクトを内蔵する方法があります.requireに適したモジュールを探して、正しいファイルを見つけたら、そのファイル名をキャッシュのキーとします.公式検索アルゴリズムの疑似コードは以下の通りです.
    require(X) from module at path Y
    1. If X is a core module,
       a. return the core module
       b. STOP
    2. If X begins with './' or '/' or '../'
    a. LOAD_AS_FILE(Y + X)
          1. If X is a file, load X as JavaScript text.  STOP
          2. If X.js is a file, load X.js as JavaScript text.  STOP
          3...
          4...
    b. LOAD_AS_DIRECTORY(Y + X)
          1. If X/package.json is a file,
             a. Parse X/package.json, and look for "main" field.
             b. let M = X + (json main field)
             c. LOAD_AS_FILE(M)
          2. If X/index.js is a file, load X/index.js as JS text.  STOP
          3...
          4...
    3. LOAD_NODE_MODULES(X, dirname(Y))
    4. THROW "not found"
    簡単に言えば、ロードされた論理または優先度は以下の通りです.
  • コアモジュール
  • かどうかを優先的に判断する.
  • コアモジュールでなければnode_を検索します.modules
  • そうでなければ、相対パスで検索する
  • .
    解析後のファイル名は、moduleオブジェクトまたは取得に応じてもよい.
    // counter-debug.js
    
    console.log(module.filename) // prints absolute path to counter.js
    console.log(__filename) // prints same as above
    // i get: "/Users/laz/repos/medium/modules/counter-debug.js"
    
    let value = 0
    
    module.exports = {
      increment: () => value++,
      get: () => value,
    
    上記の例では、モジュールの絶対パスをロードしても、解析したファイル名が分かる.ファイルとモジュールを一々マッピングする原則によって、次の二つの破損モジュールの導入の一例例の特例を導き出すことができます.
    Case Sensitivity
    小文字で敏感なファイルシステムやオペレーティングシステムでは、異なる解析後のファイルは同じファイルを指すことがありますが、キャッシュキーの名前が一致しない、すなわち異なる導入は異なるオブジェクトを生成します.
    // app-no-singleton-1.js
    const counter1 = require('./counter.js')
    const counter2 = require('./COUNTER.js')
    
    counter1.increment()
    console.log(counter1.get()) // prints 1
    console.log(counter2.get()) // prints 0, not same object as counter1
    
    /* 
    We have two different resolved filenames:
    - “Users/laz/repos/medium/modules/counter.js”
    - “Users/laz/repos/medium/modules/COUNTER.js”
    */
    上記の例では、私たちはそれぞれcounterCOUNTERという大きさだけで同じファイルを導入しています.ある大きさの書き込みに敏感なシステムであれば、例えばUBUTUでは直接に例外を投げます.
    別のファイル名に解析require(x)を使用し、xがコアモジュールに属さない場合、node_modulesフォルダが自動的に検索されます.npm 3の前に、プロジェクトはネスト方式でインストールされます.したがって、私たちのプロジェクトがmodule-aとmodule-bに依存し、module-aとmodule-bも互いに依存している場合、以下のようなファイルパスフォーマットが生成される.
    // npm2 installed dependencies in nested way
    app.js
    package.json
    node_modules/
    |---module-a/index.js
    |---module-b/index.js
        |---node_modules
            |---module-a/index.js
    このようにすれば、私達は同じモジュールに対して二つのコピーを持っています.アプリケーションにmodule-aを導入すると、次のようなファイルがロードされますか?
         // app.js
    const moduleA = require(‘module-a’)
    loads: “/node_modules/module-a/index.js”
    module-bからmodule-aをロードすると、以下のようなファイルがロードされます.
         // /node_modules/module-b/index.js
    const moduleA = require(‘module-a’)
    loads “/node_modules/module-b/node_modules/module-a/index.js”
    ただし、npm 3の後は平準化してファイルをロードし、そのファイルディレクトリ構造は以下の通りです.
        // npm3 flattens secondary dependencies by installing in same folder
    app.js
    package.json
    node_modules/
    |---module-a/index.js
    |---module-b/index.js
    しかし、この時はもう一つのシーンがあります.つまり、私たちはアプリ自体がmoduleに依存しています[email protected]と、module-bはまたmodule-に依存します[email protected]この場合もnpm 3の前と同様にネストされたディレクトリ構造が採用されます.このようにするとmodule-aと同様に異なるオブジェクトが発生しますが、その場合自体は異なるファイルとなりますので、互いに衝突することはありません.