JavaScript closures vs. anonymous functions Ask Question

JavaScript closures vs. anonymous functions Ask Question

A friend of mine and I are currently discussing what is a closure in JS and what isn't. We just want to make sure we really understand it correctly.

Let's take this example. We have a counting loop and want to print the counter variable on the console delayed. Therefore we use setTimeout and closures to capture the value of the counter variable to make sure that it will not print N times the value N.

The wrong solution without closures or anything near to closures would be:

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

which will of course print 10 times the value of i after the loop, namely 10.

So his attempt was:

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

printing 0 to 9 as expected.

I told him that he isn't using a closure to capture i, but he insists that he is. I proved that he doesn't use closures by putting the for loop body within another setTimeout (passing his anonymous function to setTimeout), printing 10 times 10 again. The same applies if I store his function in a var and execute it after the loop, also printing 10 times 10. So my argument is that he doesn't really capture the value of i, making his version not a closure.

My attempt was:

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

So I capture i (named i2 within the closure), but now I return another function and pass this around. In my case, the function passed to setTimeout really captures i.

Now who is using closures and who isn't?

Note that both solutions print 0 to 9 on the console delayed, so they solve the original problem, but we want to understand which of those two solutions uses closures to accomplish this.

ベストアンサー1

Editor's Note: All functions in JavaScript are closures as explained in this post. However we are only interested in identifying a subset of these functions which are interesting from a theoretical point of view. Henceforth any reference to the word closure will refer to this subset of functions unless otherwise stated.

A simple explanation for closures:

  1. Take a function. Let's call it F.
  2. List all the variables of F.
  3. The variables may be of two types:
    1. Local variables (bound variables)
    2. Non-local variables (free variables)
  4. If F has no free variables then it cannot be a closure.
  5. If F has any free variables (which are defined in a parent scope of F) then:
    1. There must be only one parent scope of F to which a free variable is bound.
    2. If F is referenced from outside that parent scope, then it becomes a closure for that free variable.
    3. その自由変数は閉包 F の上位値と呼ばれます。

さて、これを使って、誰がクロージャを使用し、誰が使用しないかを調べてみましょう (説明のために関数に名前を付けました)。

ケース1: 友人のプログラム

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

上記のプログラムには、fと という2 つの関数がありますg。これらがクロージャであるかどうかを確認してみましょう。

のためにf

  1. 変数をリストします。
    1. i2ローカル変数です。
    2. i自由変数です。
    3. setTimeout自由変数です。
    4. gローカル変数です。
    5. console自由変数です。
  2. 各自由変数がバインドされている親スコープを見つけます。
    1. iグローバルスコープにバインドされます
    2. setTimeoutグローバルスコープにバインドされます
    3. consoleグローバルスコープにバインドされます
  3. 関数はどのスコープで参照されますか?グローバル スコープです
    1. したがって はによって閉じiられません。f
    2. したがって はによって閉じsetTimeoutられません。f
    3. したがって はによって閉じconsoleられません。f

したがって、この関数はfクロージャではありません。

のためにg

  1. 変数をリストします。
    1. console自由変数です。
    2. i2自由変数です。
  2. 各自由変数がバインドされている親スコープを見つけます。
    1. consoleグローバルスコープにバインドされます
    2. i2のスコープにバインドされますf
  3. 関数はどのスコープで参照されますか?のスコープsetTimeout
    1. したがって はによって閉じconsoleられません。g
    2. したがってによって閉じられi2ます。g

したがって、関数は、内から参照される場合自由変数( の上位値)gのクロージャになります。i2gsetTimeout

残念なことに、あなたの友人はクロージャを使用しています。内部関数はクロージャです。

ケース2: プログラム

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

上記のプログラムには、fと という2 つの関数がありますg。これらがクロージャであるかどうかを確認してみましょう。

のためにf

  1. 変数をリストします。
    1. i2ローカル変数です。
    2. gローカル変数です。
    3. console自由変数です。
  2. 各自由変数がバインドされている親スコープを見つけます。
    1. consoleグローバルスコープにバインドされます
  3. 関数はどのスコープで参照されますか?グローバル スコープです
    1. したがって はによって閉じconsoleられません。f

したがって、この関数はfクロージャではありません。

のためにg

  1. 変数をリストします。
    1. console自由変数です。
    2. i2自由変数です。
  2. 各自由変数がバインドされている親スコープを見つけます。
    1. consoleグローバルスコープにバインドされます
    2. i2のスコープにバインドされますf
  3. 関数はどのスコープで参照されますか?のスコープsetTimeout
    1. したがって はによって閉じconsoleられません。g
    2. したがってによって閉じられi2ます。g

したがって、関数は、内から参照される場合自由変数( の上位値)gのクロージャになります。i2gsetTimeout

よかったです。クロージャを使用しています。内部関数はクロージャです。

つまり、あなたも友達もクロージャを使用しているということですね。言い争いはやめましょう。クロージャの概念と、クロージャを識別する方法について、お二人にわかりやすく説明できたと思います。

編集:すべての関数がクロージャである理由についての簡単な説明(クレジット: @Peter):

まず、次のプログラムを考えてみましょう(コントロール):

lexicalScope();

function lexicalScope() {
    var message = "This is the control. You should be able to see this message being alerted.";

    regularFunction();

    function regularFunction() {
        alert(eval("message"));
    }
}

  1. 上記の定義から、lexicalScopeと は両方ともregularFunction閉包ではないことがわかります。
  2. プログラムを実行すると、 はクロージャではないため(つまり、 を含む親スコープ内のすべての変数にアクセスできる)、警告が表示されることが予想されます。 message regularFunctionmessage
  3. プログラムを実行すると、実際に警告が発せられることがわかりますmessage

次に、次のプログラムを考えてみましょう(代替):

var closureFunction = lexicalScope();

closureFunction();

function lexicalScope() {
    var message = "This is the alternative. If you see this message being alerted then in means that every function in JavaScript is a closure.";

    return function closureFunction() {
        alert(eval("message"));
    };
}

  1. 上記の定義から、closureFunctionのみが閉包であることが分かります。
  2. プログラムを実行すると、関数がクロージャであるため警告は出ないと考えられます(つまり、 関数が作成された時点でのみ、すべての非ローカル変数にアクセスできます(message closureFunctionこの回答を見る) - は含まれませんmessage
  3. プログラムを実行すると、実際に警告が出ていることがわかりますmessage

このことから何が推測できるでしょうか?

  1. JavaScript インタープリターは、クロージャを他の関数とは異なる方法で扱いません。
  2. すべての機能はスコープチェーンクロージャには別個の参照環境はありません。
  3. クロージャは他のすべての関数と同じです。関数が属するスコープ外のスコープで参照されるときに、これをクロージャと呼びます。これは興味深いケースだからです。

おすすめ記事