しまてく

学んだ技術を書きためるブログ

サイ本 10章 モジュールと名前空間

はじめに

これは若手IT勉強会で発表する用にまとめたエントリです。
長いのでゆっくりしていってね

名前空間とは

異なるモジュール同士で名前の衝突を防ぐ目的で使われるまとまりのこと。
静的言語ではコンパイル時にエラーになるが、動的言語(JavaScript)では同名のシンボルは
後で宣言されたものが既にあるものを上書きしてしまうため要注意!

他の言語との比較

C++の場合
//hoge.h
namespace HOGE{
    void fuga(){};
}

//main.cpp
include "hoge.h"

void main(){
    HOGE.fuga();
}
Rubyの場合
module Hoge
  def fuga
    puts "fuga"
  end
end

class Piyo
  include Hoge

  def buz
    fuga
  end
end

bar = Piyo.new
bar.buz
JavaScriptの場合
var Hoge = {}; //これが名前空間!
Hoge.fuga = function(){ alert("fuga"); }

Hoge.fuga();

JavaScript名前空間の例(サイ本10章)

サイ本10.1 名前空間の作成

JavaScript開発者は自分のドメイン名を逆さまにしたものを自分専用の名前空間にすると幸せらしい。


例:cimadai.net というドメイン名をもつ開発者がUtilityという名前空間を作り、その中にいろいろ作る場合

var net; // var文で宣言しとかないとチェックのときに「undefinedだよ」と怒られる。
//netというオブジェクトがあるかどうかチェック
if( !net ) net = {};
else if( typeof net != "object" )
    throw new Error("オブジェクトじゃないから名前空間に使えないよエラー");

//net.cimadaiというオブj(ry
if( !net.cimadai ) net.cimadai = {};
else if( typeof net.cimadai != "object" )
    throw new Error("オブジェクトじゃないから名前空間に使えないよエラーその2");

//Utilityが既にあるかどうかチェック
if( net.cimadai.Utility )
    throw new Error("作ろうとしてるクラスがすでにあるよエラー");

//ここまでくれば net.cimadai は存在している。かつ、net.cimadai.Utility は存在していない。
net.cimadai.Utility = {
    Hoge: function(fuga){ /* なにかの処理 */ }
};

サイ本10.2 名前空間のインポート

JavaScriptではオブジェクトはすべて参照渡しというのを利用して。


例:既に読み込み済みのモジュールで定義されている「net.cimadai.Utility」をインポートする場合

(function(){
    var util = net.cimadai.Utility; // こうすると「util」はこの中では「net.cimadai.Utility」を指す。
})();


これってインポートって言っていいのかな?
なんというか、感覚的には単なる代入にしか思えないのですが。。

サイ本10.2.2 プライベートな名前空間とスコープとしてのクロージャ

これはjQueryの書き方が参考になると思うので、jQueryを例にします。

(function(){
...
//jQuery 18行目〜21行目
var jQuery = window.jQuery = window.$ = function( selector, context ) {
    // The jQuery object is actually just the init constructor 'enhanced'
    return new jQuery.fn.init( selector, context );
};
...
})();

上の例では

    1. window.jQuery
    2. window.$

という2つの名前(シンボル)がグローバル名前空間(この場合はwindow)に定義されます。


jQueryではこの「jQuery」「$」という中にすべての機能がパッケージングされています。
これは「クロージャ」という概念を使って実現できること。

JavaScriptクロージャ

JavaScriptでは、スコープが関数レベルでしかない。
それを利用し、関数をひとつの閉じられた空間(クロージャ)として使うことができます。

先の例の「(function(){ ... })();」というのは定型的な匿名関数を用いたクロージャ
これに囲まれた内部は、外部からは見えない閉じられた空間になっており*1、開発者が自由に
使うことができます。


jQueryのようにグローバル領域に特定の名前だけを定義するようにすれば、
名前の衝突を最小限に抑えられます、というかみんなそうするべきだと思うよ。

さらに名前の衝突を防止する方法

(function(){
...
//jQuery 13行目〜16行目
// Map over jQuery in case of overwrite
var _jQuery = window.jQuery,
// Map over the $ in case of overwrite
    _$ = window.$;
...
//jQuery 607行目〜614行目
    noConflict: function( deep ) {
        window.$ = _$;

        if ( deep )
            window.jQuery = _jQuery;

        return jQuery;
    },
...
})();


jQueryでは名前の衝突をさらに防ぐために「noConflict」という関数を用意してあります。

仕組みは、jquery.jsが読み込まれる時に

    1. window.jQuery と window.$ を匿名関数内部の変数に退避させる。
    2. noConflictが呼ばれたら退避していたwindow.jQueryとwindow.$を元々のものに戻し、jquery.jsの機能を呼び出す為のシンボルを返す。


この仕組みを用いれば、開発者は安全にシンボルを定義することができ、利用者は異なるライブラリを
使用する際に組み合わせを考えなくて済むようになります。 

John Resigによるクロージャのサンプル

http://ejohn.org/apps/learn/#48
短いサンプルなので自分で動きを考えながら脳内トレースすると楽しく学べます!

まとめ

実際にJavaScript名前空間を考慮する際には、サイ本の10章にあるような手法は用いずに
jqueryのようにまず匿名空間で囲むようにすると幸せかも。

おまけ

前に僕が書いたvimperatorのplugin(JavaScript)もクロージャ使ってるので見てね!
http://d.hatena.ne.jp/cimadai/20081004/delicious_on_vimperator

*1:意図的に見えるようにもできる。