Node.jsで同じモジュールが何度もロードされますか?

4938 ワード

Node.jsもCommonJSモジュールの機構を使って、最近InfoQの上でこの方面の問題を討論した文章があります.この記事では、Node.jsはモジュールをロードする際に、以前にモジュールがロードされていたら重複オーバーヘッドがないと述べています.この文章は初審のもので、JscexがNode.jsで使う時のモジュール化の問題を考えていましたが、規則を研究した後、いくつかの場合にはまだ何度もロードする可能性があることが分かりました.今からこの問題を分析します.
require方法を使って他のモジュールをロードすると、Node.jsは一連のディレクトリを調べに行きます.これらの経路はmodule.pathsから得られます.例えば、
[ '/Users/jeffz/Projects/node-test/node_modules',
  '/Users/jeffz/Projects/node_modules',
  '/Users/jeffz/node_modules',
  '/Users/node_modules',
  '/node_modules']
ここは私が次のモジュールを実行する時に得た結果です.Node.jsは現在のモジュールのディレクトリからnode_が表示されます.modules(ここはUnixの習慣を守らないで、下線を使っていますか?)探し始めましたが、もし再会できなかったら、上級ディレクトリのnode_を探しに行きます.modulesはルートディレクトリまでです.もちろん、実際にはNODE_もあります.PATH環境変数マークのディレクトリなど.モジュールの位置が確定したら、ノード.jsはこの位置のモジュールが既にロードされているかどうかを確認します.ロードされているなら、そのまま戻ります.
簡単に言えば、ノード.jsはモジュールの所在経路に基づいてモジュールをキャッシュする.
このように見て、「同じモジュールが何回もロードされているか」という問題は、実は「同じモジュールが異なる経路に存在するか」ということになります.簡単に考えてみれば、モジュールを使うとき、位置はいつも決まっています.例えばnpmでインストールされたモジュールを使用すると、常に現在のディレクトリのnode_に表示されます.modulesでは、ロード時にいつも同じパスを見つけます.では、「間接」が同じモジュールに依存する場合は?
例えば、私たちはExpressフレームを使いたいので、npmを使ってインストールします.
$ npm install express
[email protected] ./node_modules/express 
├── [email protected]
├── [email protected]
├── [email protected]
└── [email protected]
Expressは他のモジュールに依存しています.これらはすべてexpressモジュール自身のディレクトリに保存されています.modules/express/node_modules/mime.はい、もし私達のプロジェクト自身もmimeプロジェクトを使うなら、npmを使ってインストールすることもできます.
$ npm install mime
[email protected] ./node_modules/mime 
そして最終的に得られたのはこのような構造である.
./node_modules
├── mime
└── express
    └── node_modules
        ├── mkdirp
        ├── qs
        ├── mime
        └── connect
ここのmimeモジュールは二つの位置に現れます.名前のバージョンは全部同じです.完全にモジュールです.自分のコードに組み込まれているmimeモジュールと、express内部に搭載されているmimeモジュールは同じですか?明らかに違います.ここで同じモジュールを2回繰り返してロードし、2つのモジュールの「インスタンス」が生成されます.
このような繰り返しの負荷は一般的な状況ではあまり問題がなく、最大メモリが大きいだけで、プログラムの正確性に影響しません.しかし、私達も簡単にいくつかの意外な状況を思い付くことができます.例えば、Jscexでは、TaskオブジェクトごとにIDを与えて成長していきます.これを実現するには「種」を維持する必要があります.全体が唯一です.以前はこのシードはクローズド・パケット内で定義されていたが、Jscexモジュールは何度もロードされるので、異なるモジュールの「インスタンス」から生成されたTaskオブジェクトのIDが重複する可能性がある.もちろん、この問題を解決するのも難しくはありません.ルートオブジェクトにシードを定義するだけでいいです.異なるモジュールの「インスタンス」は同じルートオブジェクトを共有します.
もう一つの問題は隠れているように見えるかもしれません.簡単な実験で結果を見ることができます.まず、jeffz-aモジュールを定義します.ここではMyTypeタイプが露出されています.
module.exports.MyType = function () { }
npmにリリースします.jeffz-bモジュールをもう一つ書いて、jeffz-aに依存して、jeffz-aで定義されているMyTypeタイプを直接暴露します.
module.exports.MyType = require("jeffz-a").MyType;
次にjeffz-bもnpmでリリースします.もう一度テストモジュールを書いて、npmでjeffz-aとjeffz-bをインストールします.最終ディレクトリはこうなります.
./node_modules
├── jeffz-a
└── jeffz-b
    └── node_modules
        └── jeffz-a
テストモジュールでは、インスタンスとタイプの関係をテストします.
var a = require("jeffz-a");
var b = require("jeffz-b");

console.log(new a.MyType() instanceof a.MyType); // true
console.log(new b.MyType() instanceof b.MyType); // true

console.log(new a.MyType() instanceof b.MyType); // false
console.log(new b.MyType() instanceof a.MyType); // false
表面的には、jeffz-bとjeffz-aが露出しているのは同じMyTypeタイプであるべきで、それらのオブジェクトはinstance ofを通じてお互いに判断してtrueに戻るべきですが、実際にはjeffz-bの中のjeffz-aのため、私たちが直接ローディングしているjeffz-aモジュールとは異なる実例です.だから、MyTypeタイプも当然同じではありません.
これはJscexに対する影響は、Jscexの非同期モジュールがキャンセルされた時、もともとは異常対象がCacelledErrorタイプかどうかを判断することによって、Taskの状態がcanceledかそれともfaultedかを決定することにある.しかし、ノード.jsは同じモジュールを複数のインスタンスにロードする可能性があるので、たとえ捨てられたのがあるインスタンスのCacelledErrorであっても、別のインスタンスの内部で判断することはできない.そのため、現在のJscexの判定方式は異常対象を検査するisCarcelationフィールドに修正され、この問題を簡単に解決しました.
もちろん、Node.jsのこのような「重複負荷」の影響も完全にマイナスではなく、少なくとも多版共存の問題を自然に解決しました.例えば、express v 2.5.2はmime v 1.2.4に依存しますが、プログラム自体はmime v 1.2.5を使いたいです.この時、expressの内部はもちろんmime v 1.2.4を使って、私達自身のプログラムはmime v 1.2.5を使います.
場合によっては、モジュールの内部に間接的に依存されているモジュールを手動で削除し、モジュールクエリパスの共通部分に移動しなければなりません.今のところ、これらの操作は手動で行われなければならない.npmはモジュールをインストールする時、依存モジュールがすでにインストールされているかどうかに関心がないからだ.残念ながら、もしあなたがホスト形式のNode.jsサービスを利用したら、それはできないかもしれません.