Node.js の exports と require() をなんとなく理解する
ブラウザで動く JavaScript を書いてて、初めて Node.js を触ると絶対にびっくりする、モジュール。
そもそもスコープが共有されていないことに驚愕するのですが、じゃあどうやったらモジュールとかが使えるのか、ライブラリはどうやったら使えるのか、というのをざっくりまとめてみた。
Node.js におけるグローバル空間
ブラウザだと、ファイルの一番大きな部分に変数を宣言したり、そもそも宣言せずに変数を使った場合、その変数は window
オブジェクトのプロパティとして定義されます、
var test = 1; // ルートで宣言 function a() { test2 = 2; // var がついていないのでグローバル console.log(test2); // 2 console.log(window.test2); // 2 } function b() { test = 10; // 変数宣言の巻き上げによってローカル変数になる var test = 11; console.log(test); // 11 console.log(window.test); // 1 } a(); b(); console.log(test); // 1 console.log(test2); // 2
そして、 Node.js で同じことを行うと、 global
オブジェクトのプロパティになります。要するに、 window
の代わりとなるのが global
オブジェクトです。
test = 1;
console.log(global.test); // 1
この global
オブジェクトは、1ファイルごとにそれぞれ生成されます。つまり、ブラウザにおける window
オブジェクトと違い、ファイルをまたいで変数が共有されることはありません。
例えば a.js
で global.test = 1
を定義したとしても、 b.js
では global.test
は undefined
を返します。というかそもそもプロパティ自体がありません。
これによって、不用意なグローバル汚染をなくすことができます。
即時関数パターンを使わなくてもいい
これに伴って、実はブラウザ向け JavaScript プログラムでよくやる "ファイル全体を即時関数パターンで囲う" ということをしなくてよくなります。イメージ的には Node.js が勝手に囲ってくれている感じに近いです。
なので、ファイルの一番上で 'use strict'
しようと、ファイルのルートでどれだけ変数を宣言しようと、ほかのファイルには一切影響しません。
変数の公開
さきほどから散々 "ファイル間で変数の共有ができない" と言っていますが、ではどうしたら複数のファイルで変数の共有ができるでしょうか?
その答えは、 Node.js の "モジュール" という概念です。 Node.js には、モジュールをインポートしたりエクスポートして、ファイル間で情報を共有する機能があります。
そのカギとなるのが、 require
関数と exports
オブジェクトです。
require
関数
require
関数は、モジュールをインポートする関数です。つまり、モジュールを使う側が情報を得るために使います。
使い方は2つです。
var test = require('./test'); // 自ファイルと同じ階層にある test.js を読み込む var test2 = require('test2'); // node_module ディレクトリ、もしくはビルトインモジュールの test2 を読み込む
これによって、モジュールの情報が変数に入ることになります。
この関数、実は 指定したファイルを一番上から実行する 機能があります。
ブラウザの JavaScript で言うなら、 script タグで埋め込んだスクリプトが順次動くとか、途中で Element.appendChild(scriptElement);
みたいな感じで動かすのと似ています。
指定したファイルを実行し、それによって得た情報を戻り値として返す、それが require
関数です。
exports
オブジェクト
フルで書くと global.module.exports
になるのですが、 module.exports
、あるいは exports
とも書けます。
この exports
オブジェクトが、 require
関数で呼ばれた際の戻り値になります。
意味が分からないって?分かりました。 JavaScript で書きますね。 JavaScript なら皆さんも読めますね?
// test.js exports.a = 1; // index.js var test = require('./test'); // { test: 1 } console.log(test.a); // 1
test.js
で exports
オブジェクトにプロパティ a
を生やしています。つまり、 test.js
上では、 exports
は { test: 1 }
となります。
index.js
ではこれを require
関数で呼び出しています。戻り値は { test: 1 }
になります。
そうです、 exports
オブジェクトが、 require
関数の戻り値になります。
公開、非公開を意識する
※名前空間などでしっかり管理されている場合は、もう何も言うことはありませんので、どうぞ読み飛ばしてください
通常、ブラウザで動く JavaScript を書いていると、関数の公開、非公開なんて意識しません。だって全部グローバルだから。
でも、モジュールを自作するとなると厄介になります。例えば…
// これを別ファイルから使えるようにしたい function getName(id) { return get('/person/name', id); } // これは実は見られたくない function get(url, id) { var xhr = new xmlHttpRequest(); ... }
get
関数は、単純に GET リクエストを送信する関数です。この関数はバージョンアップによって引数が増えたり、そもそも生で使ってほしくないので、公開したくありません。
でも、ブラウザで動く JavaScript は全部共有してしまうため、結果的に外から見られてしまいます。
そこで、次のようにスコープを分けてしまう、という手があります。
var getName; (function () { getName = function (id) { return get('/person/name', id); }; function get(url, id) { ... } })(); getName('hoge'); // ok get('/person/name', 'hoge'); // ReferenceError: get is not defined
こうすれば、外から見られたくない関数が呼び出されることはありません。
さてこれが Node.js の場合ですが、先ほど即時関数パターンが要らなくなると書きました。つまり、こんな感じです。
getName = function (id) { return get('/person/name', id); }; function get(url, id) { ... }
でもこれだと、外部に関数を公開できません。公開する場合は、 exports
オブジェクトのプロパティにする必要があります。
// public function exports.getName = function (id) { return get('/person/name', id); }; // private function function get(url, id) { ... }
こうすることで、 Node.js でも公開する関数、非公開にする関数を分けることができました。
最後に
実は Node.js あんまり使ってません。
実際の業務ではブラウザで動く JavaScript を書いていますし、趣味で何かするときはもっぱら C# で書いてしまうので、クライアントもサーバサイドも事足りてしまうんですよね。
でも、 AWS Lamda とかだと Node.js が結構頻繁に使われますし、今の時代 Node.js を知らないと大変なこともたくさん。これだけは基本中の基本なのでしっかり押さえておかないと、後から苦しむことになると思います。
というわけで、今回はモジュールの基本概念についてまとめてみました。完全に自分用です。
次回、モジュールについて書くときは、 ES6 の Module との相互運用について、でしょうか…?