モジュールと名前空間

GoogleのClosure Toolsを触っていて、JSの名前空間がよくわかっていなかった。
教科書:JavaScript 第5版

モジュールと名前空間

当初のJSはHTML中に小さな単純なスクリプトを書く程度
現在は複雑になってきている
→外部モジュール、ライブラリが登場


JSの言語仕様では、モジュールの作成・管理という機能は提供されていないので
名前の衝突が起こらないように名前空間を使用して、モジュールを記述する必要がある。


※名前の衝突
二つのモジュールが同じ名前でグローバルプロパティを定義し、名前が衝突すると
一方のモジュールが他方のモジュールのプロパティを上書きしてしまい、モジュールがうまく動かなくなる。


再利用可能なオープンソースのJSモジュールコレクション
JSAN-JavaScript Archive Network

モジュールと名前空間の生成

任意のスクリプトから利用でき、任意のモジュールと一緒に利用可能なモジュールを
JSで記述する場合には、グローバル変数を定義しない
→他のモジュールやモジュールを使用するプログラムに変数が上書きされる危険がある


この場合には、グローバル変数を定義する代わりに
モジュール用に生成した名前空間の中にモジュール用のメソッドやプロパティをすべて定義する。


JSでは、仕様上、名前空間を定義する機構は無いので、オブジェクトを使う
通常の関数を定義して、関数の参照をグローバルオブジェクトの代わりに生成したオブジェクト中に格納する。

//名前空間用に空のオブジェクトを生成する
var Class = {};

//名前空間用に生成したオブジェクトに他のシンボルをすべて格納する
Class.define = function(data) { ... };
Class.provides = function(o, c) { ... };


ルール

  • モジュールがグローバルな名前空間にシンボルを追加する場合は、最大でも1つにするべき
  • モジュールがグローバルな名前空間にシンボルを追加する場合は、どのシンボルを追加するのかをドキュメントに明記する
  • モジュールがグローバルな名前空間にシンボルを追加する場合は、シンボル名とモジュールのファイル名に明確な関係を持たせる。


例)utilities/Class.jsとhoge/Class.jsがある場合

/**
 *  hoge/Class.js: クラス操作用のユーティリティ関数モジュール
 * 
 *  このモジュールはhogeというグローバルシンボルをひとつだけ作成する。
 *  次に名前空間用のオブジェクトを作成し、hogeオブジェクトのClassプロパティ中に保存する。
 *  全ユーティリティ関数は、この名前空間で定義される。
 *
 **/
var hoge;  //グローバルシンボルを宣言

//未定義ならオブジェクトを代入
if(!hoge) {
    hoge = {};
}

//hoge.Class名前空間を生成する
hoge.Class = {};

//名前空間にユーティリティ関数を追加していく
hoge.Class.define = function() {
    ...
}

hoge.Class.provides = function() {
    ...
}

※var hoge;でグローバルシンボルを宣言するのは、例外がスローされないようにするため。(未宣言のグローバルシンボルを参照すると例外がスローされる)


hoge.Classの様な2段階の名前空間を使えば名前衝突の可能性は減るが、Javaの様に、グローバルに一意となるパッケージ命名規則を採用すれば、名前の衝突は絶対に起こらない。
→ドメイン名を利用する(com/hoge/Class.jsの様にモジュールを作成して、com.hoge.Classという名前空間を作成する)
#Closure Library APIの構造はこういうこと?

モジュールの可用性のチェック

外部モジュールを利用するようなコードを書く場合には、名前空間をチェックしてモジュールが存在するかどうかを確認する。

var com;
if(!com || !com.hoge || !com.hoge.Class) {
     throw new Error("com/hoge/Class.js has not been loaded");
}

モジュールとしてのクラス

hoge/Classモジュールは関数をまとめたものだが、モジュールの形式に制限はない。
関数は1つだけでも、関数ではなくクラスでも、関数とクラスの組み合わせでも良い。


例)図形を表すクラスを定義するモジュール

/*
 *  com/hoge/Shapes.js:図形を表すクラスのモジュール
 *
 *  このモジュールでは、com.hoge.Shapes名前空間中にクラスを定義する。
 *  このモジュールは、com/hoge/Class.jsを利用する
 */
//Classモジュールが存在するかチェック
var com;
if(!com || !com.hoge || !com.hoge.Class) {
    throw new Error("com/hoge/Class.js has not been loaded");
}

//Classモジュールからシンボルをインポート
var define = com.hoge.Class.define;

//名前空間の定義
if(com.hoge.Shapes) {
    throw new Error("com.hoge.Shapes namespace already exists");
}

//名前空間の生成
com.hoge.Shapes = {};

//クラスの定義
com.hoge.Shapes.Circle = define({ ... });
com.hoge.Shapes.Rectangle = define({ ... });
com.hoge.Shapes.Triangle = define({ ... }); 

モジュール初期化コード

JSのモジュール=単なる関数集だけではなく、最初に読み込みが行われた時に、名前空間の設定・名前空間にプロパティを追加したりする。


読み込み時に1度だけ実行されるコードは、グローバルな名前空間を荒らさないように匿名関数中に記述して、この匿名関数を定義後すぐに呼び出すようにすれば良い。

(function() { //匿名関数の定義。名前が無いので、グローバルシンボルが追加されない
     //コードを記述する。
     //関数の中なので、どんな変数名でも安全。
}) ();     //関数定義後にすぐ呼び出し。

モジュールは完全に自己完結型であるべきで、HTML中にはJSコードを書くべきではない。=控えめなJavaScript
モジュールが自発的に初期化関数を登録し、適切なタイミングで呼び出されるようにする。

名前空間からのシンボルのインポート

com.hoge.Classの様な一意な名前空間を使用すると、長くなってしまう。
com.hoge.Class.define()が正式な関数名だが、別の変数に代入して使用すればよい。

var define = com.hoge.Class.define;


var defineだと何の定義(define)なのか分かりにくいので、わかりやすくする。

var defineClass = com.hoge.Class.define;


しかし、メソッド名を変更する(define() -> defineClass())のは良くないので、もっと短い名前空間にシンボルをインポートしても良い。

var Class = {};
Class.define = com.hoge.Class.define;

インポート出来るシンボルは「関数、オブジェクト、配列を参照しているものだけ」ということに注意する。

パブリックシンボルとプライベートシンボル

モジュールの名前空間中で定義されているシンボルがすべて、外部から使用されることを意図していない。
モジュール専用の内部関数、内部変数を持つ場合があるので、ルールを作成するようにする

  • ドキュメントに記載する
  • プライベートなシンボルはアンダーバー(_)から始めるようにする