読者です 読者をやめる 読者になる 読者になる

JavaScriptのclosureについて

JavaScript Programming

・関数,クロージャ,オブジェクトなどの細かい実装差分
手元ではnode.js v0.10.32で出力させて確認。

/* sample1 ただの関数 */
console.log("---sample1---");
function init1() {
    var num = 0;
    function disp() {
	num++;
        console.log(num);
    }
    disp();
}
init1(); // 1
init1(); // 1
init1(); // 1

/* sample2 closureができる */
console.log("---sample2---");
function init2() {
    var num = 0;
    function disp() {
	num++;
        console.log(num);
    }
    return disp;
}
var i = init2();
var j = init2();
var k = init2();
i(); // 1
i(); // 2
i(); // 3
j(); // 1 環境が変数ごとに保存されている、はず
j(); // 2
k(); // 1

/* sample3 引数つきclosure */
console.log("---sample3---");
function init3(numnum) {
    var num = 0;
    function disp() {
	num += numnum;
        console.log(num);
    }
    return disp;
}
var iii = init3(1);
var jjj = init3(10);
var kkk = init3(100);
iii(3); // 1
iii(1); // 2
iii(2); // 3
jjj(3); // 10
jjj(0); // 20
kkk();  // 100

/* sample4 引数の再代入あり */
console.log("---sample4---");
function init4(numnum) {
    var num = 0;
    function disp(numnum) {
	num += numnum;
        console.log(num);
    }
    return disp;
}
var iiii = init4(1);
var jjjj = init4(10);
var kkkk = init4(100);
iiii(3); // 3
iiii(1); // 4
iiii(2); // 6
jjjj(3); // 3
jjjj();  // NaN
kkkk(1); // 1

/* sample5 関数式+クロージャ */
console.log("---sample5---");
var init5var = function (numnum) { //関数名の有無は問われない
    var num = 1000;
    function disp() {
	num += numnum;
	console.log(num);
    }
    return disp;
};
var s = init5var(1);
var t = init5var(10);
s(); // 1001
s(); // 1002
t(); // 1010
t(); // 1020

/* sample6 class+instance */
console.log("---sample6---");
var init6var = function (numnum) {
    var num = 1000; //外部から参照できない(private)
    this.nnn = 33;  //外部から参照できる(property)
    this.disp = function () { //this.disp = method
	num += numnum;
	console.log(num);
    };
};
var ss = new init6var(1); //newあり = return an instance of Object
var tt = new init6var(100); //インスタンスとして独立
var uu = init6var(100);   //newなし = 何も入らない
console.log(ss); // { nnn: 33, disp: [Function] }
ss.disp(); // 1001
ss.disp(); // 1002
console.log(ss.num); // undefined
console.log(ss.nnn); // 33
tt.disp(); // 1100
tt.disp(); // 1200
console.log(uu); //undefined

/* sample6.5 class+newなしreturnあり */
console.log("---sample6.5---");
var init66var = function (numnum) {
    var num = 1000; //外部から参照できない(private)
    this.nnn = 33;  //外部から参照できる(property)
    this.disp = function () { //this.disp = method
	num += numnum;
	console.log(num);
    };
    return num;
};
var ss5 = init66var(1); //newなし, returnあり
var tt5 = new init66var(100);
var uu5 = init66var();
console.log(ss5); // 1000 returnがあるのでnumだけ返る
console.log(tt5); // { nnn: 33, disp: [Function] }
tt5.disp();       // 1100 ss5は値だけなのでdispを呼べない

/* sample7 クラス+返り値なし無名関数 */
console.log("---sample7---");
var init7var = function (numnum) {
    var num = 1000;
    this.disp = (function () { //this.disp = property
	num += numnum;
	console.log(num);
	// returnがないので一度実行してdispにはundefinedが入る
    })();
};
var sss = new init7var(1);   // 1001 disp内は宣言時に実行される
var ttt = init7var(33); // 1033 propertyは全部評価される
console.log(sss);      // { disp: undefined }
console.log(init7var); // [function] 関数を含んだオブジェクトを返す
console.log(sss.disp); // undefined
console.log(ttt);      // undefined returnがないので

/* sample7.5 クラス+返り値あり無名関数 */
console.log("---sample7.5---");
var init77var = function (numnum) {
    var num = 1000;
    this.disp = (function () { //this.disp = property
	num += numnum;
	return num;
    })();
    return this.disp; //このreturn文はnewなし宣言のときに返る
};
var sss5 = new init77var(100); //disp実行なし
console.log(sss5);      // { disp: 1100 }
console.log(sss5.disp); // 1100
console.log(sss5.disp); // 1100 値は増えない

/* sample8 ただの連想配列 */
console.log("---sample8---");
var init8 = {
    num: 1000,
    disp: function () {
	this.num += 1;
	console.log(this.num);
    }
};
var ssss = init8;
var tttt = init8;
console.log(ssss); // { num: 1000, disp: [function] }
ssss.disp();       // 1001
ssss.disp();       // 1002
tttt.disp();       // 1003 値渡しではなく参照渡し

/* sample9 連想配列を作る関数 */
console.log("---sample9---");
function init9func () {
    var init9 = {
        num: 1000,
        disp: function () {
 	    this.num += 1;
	    console.log(this.num);
        }
    };
    return init9;
}
var a = init9func();
var b = init9func();
console.log(a); // { num: 1000, disp: [function] }
a.disp();       // 1001
a.disp();       // 1002
b.disp();       // 1001

どこか間違ってるかも……。でもなんとなく動作がわかった。
次はクロージャを使うメリットの話。
Closures - JavaScript | MDNのPractical closuresの項がなぜそうなるのかよく分からない、onclick = makeSizer(n)にしてmakeSizer関数内も直接fontSize = size + 'px'うんぬん書いてはいけないのだろうか。
イベントとして呼ばれる前に評価が終わっちゃうからみたいなんだけど、そのあたりがまだもやもやしてる。でも眠いので今日はここまで。

追記 2015-03-09 クラス内でのプロパティやメソッドの呼び出し

n = 10000; //varを付けないとグローバルになる??
var cr = function () {
    // private
    var i = 10;
    // public
    this.n = 1000;
    // private
    function add3 () {
	i += 3;
	this.n += 3; //globalのnを指してる
    }
    // public
    this.add100 = function () {
        i += 100;
	this.n += 100; //class内のnの指してる
    }
    // public
    this.add3and100 = function () {
	add3();
        this.add100();
    }
    // public
    this.retI = function () {
	return i;
    }
}
var ss = new cr();
ss.add100();
ss.add3and100();
console.log(ss.retI()); // 213
console.log(ss.n);      // 1200  //3はss.nに足されない
console.log(n);         // 10003

node.jsとブラウザ動作でglobalの仕様が違うかもしれないけどこんな感じっぽいです。クラス内のprivateなプロパティやメソッドは、「privateなものとして扱われる」というよりも「メンバではない、クラスに関わらないものとして扱われる」。「ただクラス内に書いてある」のであって、「クラス内定義のスコープの中に書いてある」くらいだと思う。はっきりと仕様がわからないからなんとも言えないけど……。こういうのもっとしっかり仕様書読んで確認しておくべきである。そのうちね、そのうち。