手続き型音楽の日常

関数型音楽に乗り換えたい

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.jsglobal.test = 1 を定義したとしても、 b.js では global.testundefined を返します。というかそもそもプロパティ自体がありません。

これによって、不用意なグローバル汚染をなくすことができます。

即時関数パターンを使わなくてもいい

これに伴って、実はブラウザ向け 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.jsexports オブジェクトにプロパティ 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 との相互運用について、でしょうか…?

現在 0000/00/00 00:00 を生きています。