Skip to content

Latest commit

 

History

History
78 lines (54 loc) · 3.58 KB

File metadata and controls

78 lines (54 loc) · 3.58 KB

クロージャと参照

JavaScriptの一番パワフルな特徴の一つとしてクロージャが使える事が挙げられます。これはスコープがいつも外部に定義されたスコープにアクセスできるという事です。JavaScriptの唯一のスコープは関数スコープですが、全ての関数は標準でクロージャとして振る舞います。

プライベート変数をエミュレートする

function Counter(start) {
    var count = start;
    return {
        increment: function() {
            count++;
        },

        get: function() {
            return count;
        }
    }
}

var foo = Counter(4);
foo.increment();
foo.get(); // 5

ここでCounter2つのクロージャを返します。関数incrementと同じく関数getです。これら両方の関数はCounterのスコープを参照し続けます。その為、そのスコープ内に定義されているcount変数に対していつもアクセスできるようになっています。

なぜプライベート変数が動作するのか?

JavaScriptでは、スコープ自体を参照・代入する事が出来無い為に、外部から変数countにアクセスする手段がありません。唯一の手段は、2つのクロージャを介してアクセスする方法だけです。

var foo = new Counter(4);
foo.hack = function() {
    count = 1337;
};

上記のコードはCounterのスコープ中にある変数countの値を変更する事はありませんfoo.hackそのスコープで定義されていないからです。これはグローバル変数countの作成 -またはオーバーライド- の代わりになるでしょう。

ループ中のクロージャ

一つ良くある間違いとして、ループのインデックス変数をコピーしようとしてか、ループの中でクロージャを使用してしまうというものがあります。

for(var i = 0; i < 10; i++) {
    setTimeout(function() {
        console.log(i);
    }, 1000);
}

上記の例では0から9の数値が出力される事はありません。もっと簡単に10という数字が10回出力されるだけです。

匿名関数はiへの参照を維持しており、同時にforループは既にiの値に10をセットし終ったconsole.logが呼ばれてしまいます。

期待した動作をする為には、iの値のコピーを作る必要があります。

参照問題を回避するには

ループのインデックス変数をコピーする為には、匿名ラッパーを使うのがベストです。

for(var i = 0; i < 10; i++) {
    (function(e) {
        setTimeout(function() {
            console.log(e);  
        }, 1000);
    })(i);
}

外部の匿名関数はiを即座に第一引数として呼び出し、引数eiのコピーとして受け取ります。

eを参照しているsetTimeoutを受け取った匿名関数はループによって値が変わる事がありません

他にこのような事を実現する方法があります。それは匿名ラッパーから関数を返してあげる事です。これは上記のコードと同じような効果があります。

for(var i = 0; i < 10; i++) {
    setTimeout((function(e) {
        return function() {
            console.log(e);
        }
    })(i), 1000)
}