JavaScript は、世界で最も頻繁に使用されているプログラミング言語の 1 つであり、Web 世界の共通言語であり、すべてのブラウザーで使用されています。 JavaScript の誕生は Netscape の時代にまで遡ります。その中核となるコンテンツは、当時のマイクロソフトと競争し、激しいブラウザ戦争に参加するために急遽開発されました。時期尚早にリリースされたため、必然的にあまり良くない機能がいくつか生じました。
開発期間が短いにもかかわらず、JavaScript には依然として多くの強力な機能がありますが、各スクリプトがグローバル名前空間を共有するという点が異なります。
Web ページが JavaScript コードを読み込むと、そのコードはグローバル名前空間に挿入され、読み込まれた他のすべてのスクリプトと同じアドレス空間を共有します。これにより、多くのセキュリティ上の問題、競合、およびバグなどの一般的な問題が発生します。追跡も解決も困難です。
しかし、ありがたいことに、Node はサーバーサイド JavaScript の仕様をいくつか設定し、CommonJS モジュール標準も実装しました。この標準では、各モジュールは独自のコンテキストを持ち、他のモジュールとは区別されます。これは、グローバル スコープなどというものは存在せず、モジュールは相互に干渉しないため、モジュールがグローバル スコープを汚染しないことを意味します。
この章では、いくつかの異なるモジュールとそれらのロード方法について学びます。
コードを一連の明確に定義されたモジュールに分割すると、アプリケーションを制御するのに役立ちます。独自のモジュールを作成して使用する方法を以下で学びます。
Node がモジュールをロードする方法を理解する
Node では、ファイル パスまたはモジュール名を通じてモジュールを参照できます。非コア モジュールを名前で参照する場合、Node は最終的にモジュール名を対応するモジュール ファイル パスにマップします。コア機能を含むコア モジュールは、ノードの起動時にプリロードされます。
非コア モジュールには、NPM (ノード パッケージ マネージャー) を使用してインストールされたサードパーティ モジュールと、自分または同僚によって作成されたローカル モジュールが含まれます。
現在のスクリプトによってインポートされた各モジュールは、一連のパブリック API をプログラマに公開します。モジュールを使用する前に、次のように require 関数を使用してモジュールをインポートする必要があります:
var module = require(‘module_name')
上記のコードは module_name という名前のモジュールをインポートします。 , これは、コア モジュールである場合もあれば、NPM を使用してインストールされたモジュールである場合もあります。require 関数は、モジュールのすべてのパブリック API を含むオブジェクトを返します。モジュールに応じて、返されるオブジェクトは、任意の JavaScript 値、関数、または一連のプロパティを含むオブジェクト (関数、配列、または任意の JavaScript オブジェクト) になります。
エクスポート モジュール
CommonJS モジュール システムは、Node 内のファイル間でオブジェクトと関数を共有する唯一の方法です。非常に複雑なプログラムの場合は、いくつかのクラス、オブジェクト、または関数を、明確に定義された一連の再利用可能なモジュールに再構築する必要があります。モジュールのユーザーに対して、モジュールは指定したコードのみを公開します。
次の例では、Node.js 内のファイルとモジュールの間に 1 対 1 の対応関係があることがわかります。Circle コンストラクターのみをエクスポートする、circle.js というファイルを作成しました。
function Circle(x, y, r) { function r_squared() { return Math.pow(r, 2); } function area() { return Math.PI * r_squared(); } return {area: area}; } module.exports = Circle;
コード内で最も重要な行は、モジュールがエクスポートする内容を定義する最後の行です。 module は現在のモジュール自体を表す特別な変数であり、 module.exports はモジュールによってエクスポートされるオブジェクトです。この例では、ユーザーがこのモジュールが作成するモジュールを使用できるように、Circle のコンストラクターをエクスポートしました。サークルインスタンス。
一部の複雑なオブジェクトをエクスポートすることもできます。 module.exports は空のオブジェクトとして初期化され、外部に公開したいコンテンツを module.exports オブジェクトの属性としてエクスポートできます。たとえば、一連の関数を公開するモジュールを設計します。
function printA() { console.log('A'); } function printB() { console.log('B'); } function printC() { console.log('C'); } module.exports.printA = printA; module.exports.printB = printB; module.exports.pi = Math.PI;
このモジュールは、2 つの関数 (printA と printB) と数値 (pi) をエクスポートします。 呼び出しコードは次のようになります。
var myModule2 = require('./myModule2'); myModule2.printA(); // -> A myModule2.printB(); // -> B console.log(myModule2.pi); // -> 3.141592653589793
モジュールをロードします
As前述したように、require 関数を使用してモジュールをロードできます。Node.js にはグローバル名前空間の概念がないため、コード内での require の呼び出しがグローバル名前空間に影響を与えることを心配する必要はありません。モジュールが存在し、構文エラーや初期化エラーがない場合、require 関数はモジュール オブジェクトを返し、このオブジェクトを任意のローカル変数に割り当てることができます。
モジュールにはいくつかの種類があり、コア モジュール、ローカル モジュール、NPM を通じてインストールされるサードパーティ モジュールに大別できます。モジュールの種類に応じて、モジュールを参照する方法がいくつかあります。知識。
コアモジュールのロード
ノードには、コアモジュールと呼ばれる、バイナリファイルにコンパイルされるモジュールがいくつかあります。これらはパスでは参照できず、モジュール名によってのみ参照されます。コア モジュールのロード優先度が最も高くなります。同じ名前のサードパーティ モジュールが存在する場合でも、コア モジュールが最初にロードされます。
たとえば、http コア モジュールをロードして使用したい場合は、次のようにすることができます:
var http = require('http');
これは、Node API ドキュメントで定義されている htpp モジュールの API を含む http モジュール オブジェクトを返します。
ファイルモジュールのロード
絶対パスを使用してファイルシステムからモジュールをロードすることもできます:
var myModule = require('/home/pedro/my_modules/my_module');
現在のファイルに基づいた相対パスを使用することもできます:
var myModule = require('../my_modules/my_module'); var myModule2 = require('./lib/my_module_2');
注意上面的代码,你可以省略文件名的扩展名,如果Node找不到这个文件,会尝试在文件名后加上js后缀再次查找(译者注:其实除了js,还会查找json和node,具体可以看官网文档),因此,如果在当前目录下存在一个叫my_module.js的文件,会有下面两种加载方式:
var myModule = require('./my_module'); var myModule = require('./my_module.js');
加载目录模块
你还可以使用目录的路径来加载模块:
var myModule = require('./myModuleDir');
Node会假定这个目录是个模块包,并尝试在这个目录下搜索包定义文件package.json。
如果没找到,Node会假设包的入口点是index.js文件(译者注:除了index.js还会查找index.node,.node文件是Node的二进制扩展包,具体见官方文档),以上面代码为例,Node会尝试查找./myModuleDir/index.js文件。
反之,如果找到了package.json文件,Node会尝试解析它,并查找包定义里的main属性,然后把main属性的值当作入口点的相对路径。以本例来说,如果package.json定义如下:
{ "name" : "myModule", "main" : "./lib/myModule.js" }
Node就会尝试加载./myModuleDir/lib/myModule.js文件
从node_modules目录加载
如果require函数的参数不是相对路径,也不是核心模块名,Node会在当前目录的node_modules子目录下查找,比如下面的代码,Node会尝试查找文件./node_modules/myModule.js:
var myModule = require('myModule.js');
如果没找到,Node会继续在上级目录的node_modules文件夹下查找,如果还没找到就继续向上层目录查找,直到找到对应的模块或者到达根目录。
你可以使用这个特性来管理node_modules目录的内容或模块,不过最好还是把模块的管理任务交给NPM(见第一章),本地node_modules目录是NPM安装模块的默认位置,这个设计把Node和NPM关联在了一起。通常,作为开发人员不必太关心这个特性,你可以简单的使用NPM安装,更新和删除包,它会帮你维护node_modules目录
缓存模块
模块在第一次成功加载后会被缓存起来,就是说,如果模块名被解析到同一个文件路径,那么每次调用require(‘myModule')都确切地会返回同一个模块。
比如,有一个叫my_module.js的模块,包含下面的内容:
console.log('module my_module initializing...'); module.exports = function() { console.log('Hi!'); }; console.log('my_module initialized.');
然后用下面的代码加载这个模块:
var myModuleInstance1 = require('./my_module');
它会产生下面的输出:
module my_module initializing... my_module initialized
如果我们两次导入它:
var myModuleInstance1 = require('./my_module'); var myModuleInstance2 = require('./my_module');
如果我们两次导入它:
var myModuleInstance1 = require('./my_module'); var myModuleInstance2 = require('./my_module');
输出依然是:
module my_module initializing... my_module initialized
也就是说,模块的初始化代码仅执行了一次。当你构建自己的模块时,如果模块的初始化代码里含有可能产生副作用的代码,一定要特别注意这个特性。
小结
Node取消了JavaScript的默认全局作用域,转而采用CommonJS模块系统,这样你可以更好的组织你的代码,也因此避免了很多安全问题和bug。可以使用require函数来加载核心模块,第三方模块,或从文件及目录加载你自己的模块
还可以用相对路径或者绝对路径来加载非核心模块,如果把模块放到了node_modules目录下或者对于用NPM安装的模块,你还可以直接使用模块名来加载。
译者注:
建议读者把官方文档的模块章节阅读一遍,个人感觉比作者讲得更清晰明了,而且还附加了一个非常具有代表性的例子,对理解Node模块加载会很有很大帮助。下面把那个例子也引用过来:
用require(X) 加载路径Y下的模块 1. 如果X是核心模块, a. 加载并返回核心模块 b. 结束 2. 如果X以 './' or '/' or '../ 开始' a. LOAD_AS_FILE(Y + X) b. LOAD_AS_DIRECTORY(Y + X) 3. LOAD_NODE_MODULES(X, dirname(Y)) 4. 抛出异常:"not found" LOAD_AS_FILE(X) 1. 如果X是个文件,把 X作为JavaScript 脚本加载,加载完毕后结束 2. 如果X.js是个文件,把X.js 作为JavaScript 脚本加载,加载完毕后结束 3. 如果X.node是个文件,把X.node 作为Node二进制插件加载,加载完毕后结束 LOAD_AS_DIRECTORY(X) 1. 如果 X/package.json文件存在, a. 解析X/package.json, 并查找 "main"字段. b. 另M = X + (main字段的值) c. LOAD_AS_FILE(M) 2. 如果X/index.js文件存在,把 X/index.js作为JavaScript 脚本加载,加载完毕后结束 3. 如果X/index.node文件存在,把load X/index.node作为Node二进制插件加载,加载完毕后结束 LOAD_NODE_MODULES(X, START) 1. 另DIRS=NODE_MODULES_PATHS(START) 2. 对DIRS下的每个目录DIR做如下操作: a. LOAD_AS_FILE(DIR/X) b. LOAD_AS_DIRECTORY(DIR/X) NODE_MODULES_PATHS(START) 1. 另PARTS = path split(START) 2. 另ROOT = index of first instance of "node_modules" in PARTS, or 0 3. 另I = count of PARTS - 1 4. 另DIRS = [] 5. while I > ROOT, a. 如果 PARTS[I] = "node_modules" 则继续后续操作,否则下次循环 c. DIR = path join(PARTS[0 .. I] + "node_modules") b. DIRS = DIRS + DIR c. 另I = I - 1 6. 返回DIRS
更多Node.js模块加载详解相关文章请关注PHP中文网!