1. ホーム
  2. ジャバスクリプト

[解決済み】JavaScriptのクロージャと無名関数の比較

2022-04-28 04:01:16

質問

現在、友人とJSにおけるクロージャとは何か、何がクロージャでないのかについて議論しています。私たちは、それを本当に正しく理解しているかどうかを確認したいのです。

この例で考えてみよう。カウントループがあり、コンソールにカウンタ変数を遅延して表示したい。そのため、次のようにします。 setTimeout クロージャ を使用して、カウンタ変数の値を捕捉し、値NのN倍を表示しないことを確認します。

を使用しない間違った解決方法です。 クロージャ に近いもの、あるいは クロージャ となります。

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

の 10 倍の値を出力します。 i ループの後、すなわち10。

つまり、彼の試みは

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

0から9を期待通りに印刷する。

を使っていないことを伝えました。 クロージャ を捕捉するために i と言い張るが 私は、彼が使っていないことを証明しました。 クロージャ for ループの本体を別の setTimeout (自分の無名関数を setTimeout を印刷し、また10×10を印刷する。この関数を var を実行し のループを実行すると、10×10も表示されます。つまり、私の主張は 彼は本当に キャプチャー の価値 i ということで、彼のバージョンは ではなく クロージャである。

私の試みは

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

そこで、次のようにキャプチャします。 i (名前 i2 をクロージャ内で使用することができますが、今度は 戻る を別の関数にして、これを渡しています。 私の場合、setTimeoutに渡された関数は、本当にキャプチャしています。 i .

さて、クロージャを使っている人と使っていない人。

どちらの解決策も0から9をコンソールに遅延して表示するので、元の問題を解決していることに注意してください。 クロージャを使用する を実行します。

どのように解決するの?

編集後記 JavaScriptのすべての関数はクロージャであることを、次のように説明しています。 ポスト . しかし、我々はこれらの関数のサブセットを識別することにのみ興味があります。 面白い 理論的な観点から 以後、この言葉への言及は クロージャ は、特に断らない限り、この関数のサブセットを指すことになる。

クロージャについて簡単に説明します。

  1. ある関数を取り上げる。それをFと呼ぶことにしましょう。
  2. Fの変数をすべて列挙せよ。
  3. 変数には2つのタイプがあります。
    1. ローカル変数(束縛変数)
    2. 非局所変数(自由変数)
  4. もしFが自由変数を持たなければ、クロージャにはなり得ない。
  5. Fが自由変数を持つ場合(これは a の親スコープ)であれば
    1. Fの親スコープは1つだけで、その親スコープには a 自由変数が束縛されている。
    2. もしFが 参照 の外から その の親スコープのクロージャになります。 あれ 自由変数
    3. その 自由変数をクロージャFのアップバリューと呼びます。

さて、これを使ってクロージャを使う人と使わない人を考えてみましょう(説明のために関数の名前をつけています)。

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

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

上記のプログラムでは、2つの関数があります。 fg . それらがクロージャであるかどうか見てみましょう。

について 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 は自由変数 i2 (の上位値である)。 g ) いつ それは 参照 から setTimeout .

体に悪い あなたの友人はクロージャを使用しています。内側の関数はクロージャです。

ケース2:あなたのプログラム

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

上記のプログラムでは、2つの関数があります。 fg . それらがクロージャであるかどうか見てみましょう。

について 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 は自由変数 i2 (の上位値である)。 g ) いつ それは 参照 から setTimeout .

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

つまり、あなたもあなたの友人もクロージャを使っているのです。言い争いはやめましょう。クロージャの概念とその見分け方について、お二人の理解を深めることができたと思います。

編集する。 なぜすべての関数がクロージャなのか、簡単な説明です(@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. の両方がわかっています。 lexicalScoperegularFunction はクロージャではありません 上記の定義から .
  2. プログラムを実行すると 期待すること message を警告する なぜなら regularFunction はクロージャではない(つまり,クロージャにアクセスできるのは すべて を含む親スコープにある変数 message ).
  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. クロージャは他の関数と同じようなものです。ただ、クロージャと呼ぶのは、それが 参照 スコープ内の 外部 所属するスコープ というのも というのは興味深いケースです。