Node.jsにはモジュール機能があり、他のファイルの内容を読み込むことが出来る。
だが、何となくでしか使い方を理解していなかったので、調べてみた。
このエントリのコードは全て、v4.4.3
で動かした。
基本
まず、呼び出される側のファイルで、モジュール化する値やオブジェクトをexports
しておく。
そうすることで、その値やオブジェクトに外部からアクセスすることが可能になる。
呼び出す側ではrequire()
関数を使い、ファイルを読み込む。
読み込んだファイルから、exports
されている値やオブジェクトにアクセスできるようになる。
// parts.js var value = '値'; var foo = 'foo'; exports.value = value; // index.js var parts = require('./parts.js'); console.log(parts.value); // 値 console.log(parts.foo); // undefined
ファイルを読み込んでも、exports
されていない値(上記のケースではfoo
)には、アクセス出来ない。
下記のような書き方も出来る。
// parts.js module.exports = { value: '値', foo: 'foo' }; // index.js var parts = require('./parts.js'); console.log(parts.value); // 値 console.log(parts.foo); // foo
// parts.js module.exports.value = '値'; module.exports.foo = 'foo'; // index.js var parts = require('./parts.js'); console.log(parts.value); // 値 console.log(parts.foo); // foo
module.exportsとexports
module.exports
とexports
という書き方が出てきた。
何が違うのだろうか。
ネットで軽く調べた限り、同じものとして扱って構わないらしい。
exports = module.exports = {}
という図式であり、exports
はmodule.exports
を参照している、らしい。
だから、
exports.hoge = 'hoge'; exports.fuga = 'fuga'; . . .
という書き方をしていれば、exports = module.exports
の図式は壊れない。
だが例えば、module.exports
に新しくオブジェクトを割り当ててしまうと、exports = module.exports
のリンクは途切れてしまう。
それにより、以下の様な挙動が発生する。
// parts.js var value = '値'; var foo = 'foo'; module.exports = { fuga: 'fuga' }; exports.value = value; // index.js var parts = require('./parts.js'); console.log(parts.value); // undefined console.log(parts.fuga); // fuga
なぜvalue
がundefined
になってしまうのかと言えば、module.exports
に新しくオブジェクトを割り当ててしまい、リンクが途切れ、module.exports
とexports
はそれぞれ別のものに指すようになってしまったから。
parst.js
を実行させてみることで、そのことが確認出来る。
var value = '値'; var foo = 'foo'; console.log(module.exports === exports); // true module.exports = { fuga: 'fuga' }; exports.value = value; console.log(module.exports === exports); // false
元々両者は同じものを指していたが、module.exports
の再定義によってそれが変わってしまっている。
そして、require()
で読み込むのは、読み込んだファイルのmodule.exports
である。
そのため、この状態では、exports
にいくら値を設定しても、外部ファイルがそれを呼び出すことは出来ない。
まとめると、
module.exports
というオブジェクトを、require()
によって外部ファイルが呼び出すことが出来る- 初期状態では、
exports
はmodule.exports
を参照しており、両者は同じものを指している
ということになる。
require()すると、対象となるファイルの中身が実行される
require()
は、ただ単にmodule.exports
を取得するだけでなく、そのファイルの中身を実行する。
// parts.js console.log('ファイルが読み込まれました。'); // index.js var parts = require('./parts.js'); // ファイルが読み込まれました。 console.log(parts); // {} // 以下は何も表示されない require('./parts5.js');
ファイルの中身を実行してmodule.exports
を返す、というのがrequire()
の挙動のようだ。
注意点として、require()
は、同じファイルに対しては1度しか実行されないようだ。
そのため上記のケースでは、2回目のrequire()
では何も表示されなかった。
// parts.js var int = 0; int++; module.exports.int = int; // index.js var parts = require('./parts.js'); console.log(parts.int); // 1 require('./parts5.js'); console.log(parts.int); // 1
require()
の結果を別の変数に割り当てようとしても同様。require()
自体が1度しか実行されない。
// parts.js var int = 0; int++; module.exports.int = int; // index.js var parts = require('./parts.js'); console.log(parts.int); // 1 var parts2 = require('./parts5.js'); console.log(parts2.int); // 1
この、「require()
は同一ファイルに対して1度のみ実行され」、「その結果は記憶されている」という仕様により、次のような特徴が生まれる。
クロージャ
記述の通り、module.exports
というオブジェクトに入っていない値は、呼び出せない。
しかし、module.exports
を通して間接的にアクセスすることは出来る。
// parts.js var int = 10; function myFunc(arg){ console.log(arg * int); }; module.exports.myFunc = myFunc; // index.js var parts = require('./parts.js'); console.log(parts.int); // undefined parts.myFunc(2); // 20
上記の例では、エクスポートされているのはmyFunc
のみ。だから、エクスポートされていないint
にアクセスしようとしてもundefined
になる。
しかしmyFunc
においてint
を使っているため、myFunc
を通じて間接的にアクセスできる。
エクスポートしていなくても、必要に応じて値を読みに行く。これは、関数やオブジェクトでも同様である。
// parts.js function add(a,b){ return a + b; }; function showResult(a,b){ console.log( add(a,b) ); }; module.exports.showResult = showResult; // index.js var parts = require('./parts.js'); parts.showResult(2,3); // 5 console.log('showResult' in parts); // true console.log('add' in parts); // false
showResult()
の中で、エクスポートされていないadd()
が使われているが、問題なく動く。
だが、add()
そのものがparts
に入っているわけではない。あくまでも、必要になった時(この場合はshowResult()
実行時)に呼び出されるだけである。
この特徴を利用すると、クロージャのような仕組みを作ることが出来る。
// parts.js var int = 0; function increment(){ int++; }; function get(){ return int; }; module.exports.increment = increment; module.exports.get = get; // index.js var parts = require('./parts.js'); console.log(parts.inc); // undefined console.log(parts.get()); // 0 parts.increment(); console.log(parts.get()); // 1
変数int
はエクスポートされていないため、それに直接アクセスすることは出来ない。
だが、関数get()
やincrement()
を通して、操作、取得することが出来る。
ディレクトリ
require()
の引数にディレクトリを渡すことも出来る。
そうすると、そのディレクトリのindex.js
を読み込む。
その時、index.js
で他のファイルを読み込みエクスポートしておくことで、複数のファイルを簡単に読み込める。
// ディレクトリ構造 - app.js - modules/ - index.js - data.js - cal.js
// data.js var int = 1; module.exports.int = int; // cal.js function add(a,b){ return a + b; }; module.exports.add = add; // index.js module.exports.cal = require('./cal.js'); module.exports.data = require('./data.js');
このような状況では、以下の様な書き方をすることで、data.js
やcal.js
にアクセスすることが出来る。
// app.js var myModules = require('./modules'); console.log(myModules.data.int); // 1 console.log( myModules.cal.add(myModules.data.int,2) ); // 3