あどけない話

インターネットに関する技術的な話など

無名コンストラクタ

web 2.0 EXPO の JavaScript Labs で、リクルートの川崎さんが講演されていました。口頭では触れていませんでしたが、講演資料の中で無名コンストラクタが紹介されていました。

無名コンストラクタを使うと、無名関数と同様に、グローバルの名前空間を汚さず、コードを実行できます。長くなりますが、C とも比べながら、順を追って、僕なりに説明してみたいと思います。

Cの関数呼び出し

ANSI の C では、関数呼び出しの

foo(1);

(*foo)(1);

は等価です。

JavaScript の関数呼び出し

JavaScript でも同じように、

foo(1);

(foo)(1);

は等価です。'*' がないところが違いますね。

無名関数

C では関数に必ず名前をつけないといけません。一方、JavaScript では、無名関数が用意されています。

たとえば、

function foo(x) {
    return x + 1;
}

var foo = function(x) {
    return x + 1;
};

は、(名前に "." が含まれていないなら)等価です。

function で定義した関数を変数に代入しなければ、関数に名前がなく、無名関数となります。

無名関数の実行

var foo = function(x) {
    return x + 1;
};
(foo)(1);

のように、わざわざ foo という名前を付けなくても定義した関数を実行できます。これは、関数呼び出しの関数名前の部分に、関数の定義を書くだけです。

(function(x) { return x + 1; })(1);

関数の定義は、括弧で囲む必要があります。括弧で囲まない以下の例は、エラーとなります。

function(x) { return x + 1; }(1);

無名関数の利用例

グローバルの名前空間を汚さずにコードを実行したい場合、無名関数を利用できます。

(function() {
    var weekOfDays = ['日', '月', '火', '水', '木', '金', '土' ];
    var today = new Date();
    var weekOfDay = today.getDay();
    alert('今日は' + weekOfDays[weekOfDay] + '曜日です');
})();

変数 today などは、関数のローカル変数ですから、グローバルの名前空間を利用しなくて済む訳です。

new の内側

JavaScript のコンストラクタは、単なる関数です。その関数の中で、this を使うと、それが新しいオブジェクトを参照するので、そのオブジェクトのプロパティを設定できます。

var Func = function(x) { this._x = x; };
var obj = new Func(1);
// => {_x: 1}

JavaScript を学んだとき、this を含まない関数に対し new するとどうなるんだろうと疑問に思った方もいるかもしれません。疑問を解消するために、new の内部で何が起こっているか調べてみましょう。

JavaScript で new Func(args) と実行すると、内部で次のような工程で新しいオブジェクトが作られます。

  • 新しいオブジェクトを作る
  • Func.prototype がオブジェクトなら、新しいオブジェクトのプロトタイプを Func.prototype に設定する
  • 新しいオブジェクトにコンストラクタを適応する
  • 上記の返り値がオブジェクトなら、上記の返り値を返す
  • さもなくば、新しいオブジェクトを返す

new を関数 New で無理矢理表すと以下のような感じになります。

function New(Func, args) {
    var obj = {};
    if (Func.prototype instanceof Object) {
	obj.__proto__ = Func.prototype;
    }
    var ret = Func.apply(obj, args);
    if (ret instanceof Object) {
	return ret;
    } else {
	return obj;
    }
}

無名コンストラク

new の内側を見ると、this を含まない関数に new してもよさそうです。その関数は、新しいオブジェクトに対して実行されますが、関数側ではそのオブジェクトに関与しないだけの話です。

var Foo = function() {
    var weekOfDays = ['日', '月', '火', '水', '木', '金', '土' ];
    var today = new Date();
    var weekOfDay = today.getDay();
    alert('今日は' + weekOfDays[weekOfDay] + '曜日です');
};

new Foo();
// => Foo が実行され、新しく作られたオブジェクトは捨てられる

new には無名関数を指定できますから、Foo という名前は不要です。

new function() {
    var weekOfDays = ['日', '月', '火', '水', '木', '金', '土' ];
    var today = new Date();
    var weekOfDay = today.getDay();
    alert('今日は' + weekOfDays[weekOfDay] + '曜日です');
};
// => Foo が実行され、新しく作られたオブジェクトは捨てられる

これはグローバルな名前空間は汚さずに、コードを実行したことに他なりません!