Skip to content

JavaScriptでのモジュールパターン

一般的なオブジェクトの書き方・作り方

var Person = {
    name: "Taro",
    age: 10,

    init: function() {
        // 何らかの初期化処理
    },

    getName: function() {
        return this.name;
    },

    getAge: function() {
        return this.age;
    },

    // オブジェクト内部のみで使うメソッド定義
    doSomething function() {
        ...
    }
};

Person.init();

このような書き方は簡潔で分かりやすい反面,すべてのプロパティを外部から変更可能というデメリットがある.例えば

Person.name = "Hanako";
Person.age = 9;

などと書けてしまうので,nameageプロパティの値が保証されない.しかも,doSomethingメソッドは本来,オブジェクト内部のみで使うつもりなのに,普通に外部から呼び出し・実行できてしまう.このことは,グループ開発時にマイナスの影響を及ぼすかも知れない(例えば,意図しないバグを生む,など).

モジュール・パターン

そこで,即時関数を利用してオブジェクトのプロパティやメソッドをカプセル化することが行われる.

var Person = (function () {
    var _name = "Taro";
    var _age = 10;

    function _init() {
        // 何らかの初期化処理
    }

    function _getName() {
        return _name;
    }

    function _setAge(year) {
        _age = year;
    }

    // オブジェクト内部のみで使うメソッド定義
    function _doSomething() {
        ...
    }

    // 初期化を実行する
    _init();

    // 外部に公開するメソッドを定義する
    return {
        getName: _getName,
        setAge: _setAge,
    };
}());

このように,即時関数の内部にプロパティ(変数)とメソッド(関数)を定義し,最後のreturn文で公開メソッドだけをオブジェクトで返すことでカプセル化が実現できる.すなわち,プロパティ_name_ageは,外部からの直接的な変更が不可能となる.

ワンポイント

オブジェクト内部からしかアクセスできないプロパティやメソッドには,慣例として先頭に_(アンダースコア)をつけ,プライベートメンバであることを明示的に示すことが多い

上記のような書き方をすると,Personオブジェクトを(開発者の意図通りに)制限することができる.例えば_doSomethingメソッドは外部から実行できないし,公開メソッドを追加・変更することも容易である(returnで返すオブジェクトを変更するだけ).Personオブジェクトを使う側のユーザは

var name = Person.getName();  // 'Taro'
Person.setAge(15);            // プライベートプロパティ_ageを15に変更

のように,公開メソッドだけを意識して利用すれば良い.

名前空間の利用

名前空間とは,次のような概念のことをいう.

  • 同一の名前空間中では,同じ名前の存在が許されない
  • 名前空間が異なっていれば,同じ名前の存在を許す

要するに,自分の担当モジュール(あるいは自分たちのグループで作っているコード全体)を「ただ一つの名前空間」で囲んでしまえば,他の人や他のグループがどのような名前のグローバル変数を定義したとしても「名前の競合は発生しない」ことになる.

実はJavaScriptには「名前空間を規定するための仕組み」そのものは存在しないが,連想配列を利用することで疑似的に名前空間を作ることは可能である.

var App = {};  // 名前空間代わり

App.Person = (function () {
    // いろいろ書く
})();

JavaScriptで書かれたソースファイルの分割

プログラムが大きくなってくると,モジュール単位で,あるいはもう少し大きい機能の単位で「ソースファイルを分割して管理」することが普通である.しかしながら,JavaScriptでは(ES2015の登場までは)言語の仕様として分割ファイルをサポートしてこなかった!ため,これを実現するためにはサードパーティ製のライブラリ(CommonJSとかBabelとか)を利用する必要があった.

ただ,本講義で扱うような(比較的)小さい規模のソフトウェアであれば,上述したモジュール・パターン(や名前空間)を使ってモジュール別にソーススクリプトを書いておき,それらをHTML側で複数読み込むようにすればとりあえずの実現は可能である.

...
<script type="text/javascript" src="module1.js"></script>
<script type="text/javascript" src="module2.js"></script>
...

ただし,むやみやたらにファイルを分割してしまうと,ブラウザへのリクエスト回数が増え,そのせいでページ読み込みが遅くなる可能性もある.各ファイルのサイズは確かに小さくなるかもしれないが,全体の「重さ」は(一般的には)増えるので,分割のし過ぎには十分に注意すること.(HTTPプロトコルの特性上,一つの大きなファイルを読み込むよりも,複数の小さなファイル読み込む方が時間がかかる)