ノード:詳細なモジュールメカニズム
1.CommonJS仕様
-
CommonJS(http://www.commonjs.org)仕様の出現は、Webサーバー、デスクトップ、コマンドラインツール、ブラウザーを含むJavaScriptのエコシステムを構築することを目的としています。CommonJSは実際には新しい言語ではなく、新しいインタープリターであるとは言えません。実際、これは単なる概念または仕様です。
-
フロントエンドJavaScriptのどのギャップを具体的に埋めますか?実際、これには、バイナリ、エンコーディング、IO、ファイル、システム、アサーションテスト、ソケット、イベントキュー、ワーカー、コンソールなど、フロントエンドJavaScriptでカバーされていない多くのことが含まれます。
-
CommonJSはこれらの問題を解決するためにいくつかの仕様を開発し、Node.jsはこれらの仕様の実装です。Node.js自体は、モジュールを導入する方法としてrequireメソッドを実装します。同時に、NPMは、CommonJSで定義されたパッケージ仕様に基づいて、依存関係管理や自動モジュールインストールなどの機能も実装します。ここでは、Node.jsのrequireメカニズムと、パッケージ仕様に基づいたNPMのアプリケーションについて詳しく説明します。
2.単純なモジュールの定義と使用
まず、円の面積を計算するための簡単に定義されたモジュールを見てみましょう:
// circle.js
var PI = Math.PI;
exports.area = function(r){
return PI * r * r;
}
// app.js
var circle = require('./circle.js');
console.log(circle.area(2)); // 4PI
モジュールの呼び出しは非常に便利で、呼び出す必要のあるファイルのみが必要であることがわかります。Node.jsによると、モジュールの定義と呼び出しはパッケージ化されており、非常にユーザーフレンドリーです。
3.exportsとmodule.exportsの違い
小さな栗を見てみましょう:
// rocker.js
module.exports = 'ROCK N ROLL!';
exports.name = function() {
console.log('Let them know that we still rock n roll');
};
//app.js
var rocker = require('./rocker.js');
rocker.name(); // TypeError: Object ROCK N ROLL! has no method 'name'
-
ロッカーモジュールはexports.nameを完全に無視し、「ROCKIT!」の文字列を返しました。
-
上記の例を通して、モジュールがモジュールインスタンスである必要はないことに気付くかもしれません。モジュールには、ブール値、数値、日付、JSON、文字列、関数、配列など、任意の有効なJavaScriptオブジェクトを指定できます。
-
module.exportsに値を明示的に設定しない場合、エクスポートの属性はmodule.exportsに割り当てられ、それが返されます。
正しい使用法は次のとおりです。
// rocker.js
module.exports = function(name, age) {
this.name = name;
this.age = age;
this.about = function() {
console.log(this.name +' is '+ this.age +' years old');
};
};
//app.js
var Rocker = require('./rocker.js');
var rocker = new Rocker('yivi',18);
rocker.about(); //yivi is 18 years old
- モジュールを特別なオブジェクトタイプにする場合は、module.exportsを使用します。モジュールを従来のモジュールインスタンスにする場合は、exportsを使用します。
module.exportsに属性を割り当てた結果は、エクスポートに属性を割り当てた場合と同じです。次の例を見てください。
module.exports.name = function() {
console.log('My name is Lemmy Kilmister');
};
//等同于
exports.name = function() {
console.log('My name is Lemmy Kilmister');
};
- Module.exportsは本物であり、エクスポートは単なる補助メソッドです。そうは言っても、モジュールのオブジェクトタイプを従来のモジュールインスタンスから別のものに変更したい場合を除いて、エクスポートは依然として推奨されるオブジェクトです。
4.モジュールのロード戦略
Node.jsモジュールは、次の2つのカテゴリに分類されます。
- 1つはネイティブ(コア)モジュールで、もう1つはファイルモジュールです。ネイティブモジュールは、Node.jsソースコードがコンパイルされるときにバイナリ実行可能ファイルにコンパイルされ、読み込み速度が最も速くなります。
- 別のタイプのファイルモジュールが動的にロードされ、ロード速度はネイティブモジュールよりも遅くなります。
ただし、Node.jsはネイティブモジュールとファイルモジュールの両方をキャッシュするため、2回目の要求中に繰り返しオーバーヘッドが発生することはありません。ネイティブモジュールはすべてlibディレクトリで定義されており、ファイルモジュールは不確実です。
-
ファイルモジュールをロードする作業は、主に起動時にロードされたネイティブモジュールモジュールによって実現および完了され、プロセスはrunMain静的メソッドを直接呼び出します。
-
実際、ファイルモジュールでは、3種類のモジュールに分かれています。これらの3種類のファイルモジュールはサフィックスによって区別され、Node.jsはサフィックス名に従ってロード方法を決定します。
**。js:** fsモジュールを介してjsファイルを同期的に読み取り、コンパイルして実行します。
**。node:** C / C ++で記述されたアドオン。dlopenメソッドを介してロードします。
**。json:**ファイルを読み取り、JSON.parseを呼び出して解析してロードします。
jsファイルをコンパイルする過程で、Node.jsはjsファイルの先頭と末尾をラップし、次のように全世界を汚染しないようにモジュールにパックします。
(function (exports, require, module, __filename, __dirname) {
var circle = require('./circle.js');
console.log('The area of a circle of radius 4 is ' + circle.area(4));
});
- このコードは
runInThisContext
、vmネイティブモジュールのメソッドによって実行され(evalに似ていますが、コンテキストが明確で、グローバルを汚染しません)、特定の関数オブジェクトとして返されます。最後に、渡されたモジュールオブジェクトexports
、require
メソッド、モジュール、ファイル名、ディレクトリ名が実際のパラメータとして使用され、実行されます。 - これが、requireがapp.jsファイルで定義されていない理由ですが、このメソッドは存在します。Node.jsのAPIドキュメントを見ることができることから
__filename
、__dirname
、module
、exports
いくつかの変数が定義されているが存在しません。どちら__filename
と__dirname
プロセスで得られた分析後のパスが渡されたファイルを見つけること。モジュール変数は、モジュールオブジェクト自体でexports
あり、モジュールのコンストラクターで初期化された空のオブジェクト({}、nullではない)です。 - このメインファイルでは、requireメソッドを介して他のモジュールを導入できます。実際、このrequireメソッドは実際にloadメソッドを呼び出します。
- loadメソッドは、モジュールをロード、コンパイル、およびキャッシュした後、モジュールのエクスポートオブジェクトを返します。これが、circle.jsファイルのexportsオブジェクトで定義されたメソッドのみを外部から呼び出すことができる理由です。
上記のモジュールロードメカニズムは、lib /module.jsで定義されています。
5.requireメソッドでのファイル検索戦略
- ファイルモジュールキャッシュからロード
- ネイティブモジュールからロード
- ファイルからロード
requireメソッドは、次のパラメーターを受け取ります。
http、fs、pathなど、ネイティブモジュール。
/ modまたは... / mod、相対パスのファイルモジュール。
/ pathtomodule / mod、ファイルモジュールの絶対パス。
mod、非ネイティブモジュールのファイルモジュール。
ロードされたファイルモジュールごとに、モジュールオブジェクトが作成されると、モジュールには、現在のファイルのパスに基づいて値が計算されるパス属性があります。
パスの生成規則は次のとおりです。現在のファイルディレクトリからnode_modulesディレクトリを検索し、次に親ディレクトリに入り、親ディレクトリの下にあるnode_modulesディレクトリを探し、ルートディレクトリの下にあるnode_modulesディレクトリまで順番に繰り返します。
絶対パスのファイルが必要な場合、検索は各node_modulesディレクトリを裏返しにトラバースしません。これが最速です。
6.パッケージ構造
前述のように、JavaScriptにはパッケージ構造がありません。CommonJSは、この現状の変更に取り組んでいるため、パッケージ構造の仕様を定義しています(http://wiki.commonjs.org/wiki/Packages/1.0)。NPMの登場は、CommonJS仕様に基づいて、パッケージのインストールとアンインストール、依存関係の管理、バージョン管理などの問題を解決することです。requireの検索メカニズムが明確になったら、パッケージの詳細を見てみましょう。
package.jsonファイルは、パッケージの最上位ディレクトリに存在する必要があります。
バイナリファイルはbinディレクトリに含まれている必要があります。
JavaScriptコードはlibディレクトリに含まれている必要があります。
ドキュメントはdocディレクトリにある必要があります。
単体テストはテストディレクトリにある必要があります。
上記のrequire
検索プロセスから、Node.jsがターゲットファイルを見つけられない場合、現在のディレクトリをパッケージとしてロードしようとすることがわかります。したがって、package.jsonファイルの最も重要なフィールドはmainです。しかし実際には、この場所はNode.jsの拡張であり、このフィールドは標準の定義には含まれていません。必要なのrequire
はmain属性だけです。ただし、さらに、パッケージはインストール、アンインストール、依存関係管理、バージョン管理、およびその他のプロセスを受け入れる必要があるため、CommonJSはpackage.jsonファイルに対して次の必須フィールドを定義します。
name:パッケージ名。NPMで一意である必要があります。スペースを含めることはできません。
説明:パッケージの概要。通常、一部のリストに表示されます。
バージョン:バージョン番号。セマンティックバージョン番号(http://semver.org/)、通常はxyz。このバージョン番号は非常に重要であり、バージョン管理の場面でよく使用されます。
キーワード:キーワードの配列。NPMでのカテゴリ検索に使用されます。
メンテナ:パッケージメンテナの配列。array要素は、name、email、webの3つの属性を含むJSONオブジェクトです。
寄稿者:パッケージ寄稿者の配列。1つ目は、パッケージの作成者です。オープンソースコミュニティでは、提出されたパッチがマスターブランチにマージされる場合、パッチを提供した人を追加する必要があります。形式には名前と電子メールが含まれます。
バグ:バグを送信できるURLアドレス。メールアドレス(mailto:mailxx @ domain)またはWebページアドレス(http:// url)にすることができます。
ライセンス:パッケージで使用されるライセンス。
リポジトリ:ソースコードがホストされているアドレスの配列。
依存関係:現在のパッケージに必要な依存関係。この属性は非常に重要です。NPMはこの属性を使用して、依存パッケージを自動的にロードできるようにします。
スクリプト:操作中に実行するファイルを指定するか、コマンドを実行します。
7.Node.jsモジュールとフロントエンドモジュールの類似点と相違点
通常、フロントエンドとバックエンドに適用できるモジュールがいくつかありますが、スクリプトタグを介してブラウザによってロードされるJavaScriptファイルは裸であり、Node.jsは最終実行プロセスへのロード中にパッケージ化されるため、変数は各ファイルのはクロージャにカプセル化されており、グローバル変数を汚染しません。
したがって、フロントエンドとバックエンドの一貫性の問題を解決するために、クラスライブラリ開発者はクラスライブラリコードをクロージャでラップする必要があります。
したがって、フロントエンドとバックエンド用のユニバーサルJavaScriptライブラリを設計する場合、次のような同様の判断があります。
if (typeof exports !== "undefined") {
exports.EventProxy = EventProxy;
} else {
this.EventProxy = EventProxy;
}
つまり、exportsオブジェクトが存在する場合、ローカル変数はexportsオブジェクトにマウントされ、存在しない場合は、グローバルオブジェクトにマウントされます。