function メモリリーク - JavaScriptクロージャはどのように機能しますか?




使いどころ 引数 (25)

現在、幼児を教える6歳の父親(正式な教育なしでコーディングするという初心者からの訂正が必要になる)の父親として、私はレッスンがハンズオン・プレイを通して最高のスティックとなると考えています。 6歳の人が閉鎖が何であるかを理解する準備ができていれば、彼らは自分自身を持つために十分な年齢です。私はjsfiddle.netにコードを貼り付け、ちょっと説明して独特の曲を作るためにそれらを残しておくことをお勧めします。以下の説明文は、おそらく10歳の方に適しています。

function sing(person) {

    var firstPart = "There was " + person + " who swallowed ";

    var fly = function() {
        var creature = "a fly";
        var result = "Perhaps she'll die";
        alert(firstPart + creature + "\n" + result);
    };

    var spider = function() {
        var creature = "a spider";
        var result = "that wiggled and jiggled and tickled inside her";
        alert(firstPart + creature + "\n" + result);
    };

    var bird = function() {
        var creature = "a bird";
        var result = "How absurd!";
        alert(firstPart + creature + "\n" + result);
    };

    var cat = function() {
        var creature = "a cat";
        var result = "Imagine That!";
        alert(firstPart + creature + "\n" + result);
    };

    fly();
    spider();
    bird();
    cat();
}

var person="an old lady";

sing(person);

説明書

DATA:データは事実の集合です。数字、言葉、測定値、観測値、あるいは物事の説明だけでも可能です。あなたはそれに触れることはできません、それをにおいまたは味わうことはできません。書き留めて、話し、聞くことができます。あなたはそれを使用して、コンピュータを使って触覚と風味をつくることができます。それは、コードを使用してコンピュータによって有用にすることができます。

コード:上のすべてのコードコードと呼ばれます。JavaScriptで書かれています。

JAVASCRIPT:JavaScriptは言語です。英語やフランス語、中国語などの言語があります。コンピュータや他の電子プロセッサが理解できる多くの言語があります。 JavaScriptがコンピュータによって理解されるためには、インタプリタが必要です。ロシア語だけを話す教師が学校であなたのクラスを教えるようになると想像してみてください。教師が「всесадятся」と言うと、クラスは理解できません。しかし、幸いなことに、あなたのクラスにはロシアの弟子がいて、誰もが "すべての人が座っている"ということを教えてくれます。クラスはコンピュータのようで、ロシアの生徒は通訳です。 JavaScriptの場合、最も一般的なインタプリタはブラウザと呼ばれます。

ブラウザ:コンピュータ、タブレット、または携帯電話でインターネットに接続してウェブサイトにアクセスすると、ブラウザが使用されます。あなたが知っているかもしれない例はInternet Explorer、Chrome、Firefox、Safariです。ブラウザはJavaScriptを理解し、コンピュータに何をする必要があるかを伝えることができます。JavaScript命令は関数と呼ばれます。

機能:JavaScriptの関数はファクトリのようです。それは内部に1台のマシンしかない小さな工場かもしれません。それとも、多くの機械が別の仕事をしている他の小さな工場もたくさんあるかもしれません。実際の生活服の工場では、糸の入った織物や糸のボビン、Tシャツやジーンズが出てくるかもしれません。私たちのJavaScript工場はデータを処理するだけで、縫うことができず、穴を掘ったり金属を溶かしたりすることはできません。JavaScriptファクトリでは、データが入りデータが出力されます。

このすべてのデータはちょっと退屈ですが、実際にはとてもクールです。我々はロボットに夕食のために何を作るべきかを知らせる機能を持っているかもしれない。あなたとあなたの友人を私の家に招待してみましょう。あなたは鶏の脚が一番好きです、私はソーセージが好きです、あなたの友人はいつもあなたが望むものを望み、私の友人は肉を食べません。

私は買い物に行く時間がないので、機能は、私たちが冷蔵庫で持っているものが意思決定をするのを知る必要があります。各成分は調理時間が異なり、ロボットが同時にすべてを熱く召し上がるようにします。私たちが好きなものについてのデータを関数に提供し、関数が冷蔵庫に「話す」ことができ、関数がロボットを制御できるようにする必要があります。

関数は通常、名前、カッコおよび中カッコを持ちます。 このような:

function cookMeal() {  /*  STUFF INSIDE THE FUNCTION  */  }

なお/*...*///ブラウザによって読まれているコードを停止します。

名前:必要な単語だけを呼び出すことができます。"cookMeal"の例は、2つの単語を結合し、最初に大文字を付けるのが典型的ですが、これは必須ではありません。それにはスペースを入れることはできず、それ自身の数字にすることはできません。

PARENTHESES: "括弧" ()は、JavaScriptファクトリのドアのレターボックス、または情報パケットを工場に送るための通りのポストボックスです。ポストボックスにマークが付い cookMeal(you, me, yourFriend, myFriend, fridge, dinnerTime)いる場合があります。

BRACES:このような "ブレース" {}は、私たちの工場の色付きの窓です。工場内から見ることができますが、外から見ることはできません。

上記の長いコード例

私たちのコードはfunctionという単語から始まります。したがって、それは1つです!そして、関数の名前は歌います - それは関数が何であるかについての私自身の記述です。次にカッコ()。カッコは常に関数のためにあります。時々彼らは空であり、時々彼らは何かを持っています(person)。この後、このようなブレースがあります{。これは関数sing()の開始を示します。それは歌()の終わりをこのようにマークするパートナーを持っています}

function sing(person) {  /* STUFF INSIDE THE FUNCTION */  }

だから、この機能は歌と関係があり、人についての何らかのデータが必要かもしれません。そのデータで何かをするための指示が内部にあります。

さて、関数sing()の後、コードの終わり近くでは、行

var person="an old lady";

VARIABLE:文字varは "変数"を表します。変数はエンベロープのようなものです。外側では、この封筒は「人」と記されています。内部には、私たちの機能が必要とする情報を含む紙切れが入っています。いくつかの文字とスペースは、「老婦人」と読むフレーズを作る文字列(文字列と呼ばれます)のようにつながっています。私たちのエンベロープには、数値(整数と呼ばれる)、命令(関数と呼ばれる)、リスト(配列と呼ばれる)などの他の種類のものを含めることができます。この変数はすべての中括弧の外側に書かれているため、中括弧{}内にあるときに色付きのウィンドウを見ることができるため、この変数はコードのどこからでも見ることができます。これを「グローバル変数」と呼びます。

GLOBAL VARIABLE:はグローバル変数です。つまり、「老婦人」から「若者」に値を変更すると、そのは再び変更するまで若者になります。コードはそれが若い男であることを見ることができます。押しF12ボタンまたはブラウザのデベロッパーコンソールを開き、この値が何であるかを確認するために、「人」と入力するには、[オプションの設定を見てください。タイプperson="a young man"して変更した後、再び "人"と入力して変更されたことを確認してください。

この後、私たちは

sing(person);

この行は、犬を呼び出しているかのように関数を呼び出しています

歌いなさい、来て、を手に入れよう!

ブラウザがこの行に達したJavaScriptコードをロードすると、関数が開始されます。私は最後に行を置いて、ブラウザが実行するために必要なすべての情報を持っていることを確認します。

関数はアクションを定義します - 主な機能は歌についてです。これにはfirstPartという変数が含まれています。この変数は、曲の各節に適用される人についての歌に適用されます。「そこにいる人+人+人を飲み込んだ人」あなたが入力した場合FIRSTPARTコンソールに変数は関数の中でロックアップされているので、あなたは答えを得ることはありません-ブラウザは、中括弧の彩色の窓の内部を見ることができません。

クロージング:クロージャーは大きなsing()関数の中にある小さな関数です。大きな工場の中の小さな工場。彼らはそれぞれ内部の変数が外部から見ることができないことを意味する独自のブレースを持っています。だからこそ、変数の名前(生き物結果)はクロージャで繰り返すことができますが、値は異なります。これらの変数名をコンソールウィンドウに入力すると、色付きウィンドウの2つのレイヤーによって隠されているため、その値は取得されません。

クロージャーはすべて、sing()関数の変数firstPartと呼ばれるものが何であるかを知っています。

閉鎖後にラインが来る

fly();
spider();
bird();
cat();

sing()関数は、これらの関数のそれぞれを与えられた順序で呼び出します。その後、sing()関数の処理が行われます。

どのようにあなたは、彼らが構成する概念(例えば、関数、変数など)の知識を持つ人にJavaScriptクロージャを説明しますが、クロージャ自身を理解していませんか?

私はWikipediaで与えられたSchemeの例を見ましが、残念ながらそれは役に立たなかった。


あなたは5歳の子供に閉鎖を説明できますか?*

私はまだGoogleの説明がうまく機能し、簡潔であると思う:

/*
*    When a function is defined in another function and it
*    has access to the outer function's context even after
*    the outer function returns.
*
* An important concept to learn in JavaScript.
*/

function outerFunction(someNum) {
    var someString = 'Hey!';
    var content = document.getElementById('content');
    function innerFunction() {
        content.innerHTML = someNum + ': ' + someString;
        content = null; // Internet Explorer memory leak for DOM reference
    }
    innerFunction();
}

outerFunction(1);​

* AC#質問


本文:この回答は質問があったときに書かれました:

古いアルバートのように言った: "あなたは6歳に説明できないなら、あなたは本当に自分でそれを理解していない" ...まあ、私は27歳の友人にJSの閉鎖を説明しようとし、完全に失敗しました。

誰かが私が6歳で、その話題に興味をそそられていると考えることができますか?

私は文字通り最初の質問をしようとした唯一の人の一人であると確信しています。 それ以来、質問は何度か突然変異してしまったので、私の答えは今や信じられないほどばかげているかもしれません。 うまくいけば、物語の一般的なアイデアは、いくつかのために楽しいままです。

私は、難解な概念を説明するときのアナロジーとメタファーの大きなファンです。私は物語で私の手を試してみましょう。

昔々:

プリンセスがいた...

function princess() {

彼女は冒険がいっぱいの素敵な世界に住んでいました。 彼女は彼女の魅力的な皇太子と出会い、ユニコーン、戦った竜、話す動物に遭遇し、彼女の世界の周りを飛び回った。

    var adventures = [];

    function princeCharming() { /* ... */ }

    var unicorn = { /* ... */ },
        dragons = [ /* ... */ ],
        squirrel = "Hello!";

    /* ... */

しかし、彼女はいつも家事や大人の鈍い世界に戻らなければなりません。

    return {

そして彼女はしばしば彼女に彼女の最新の素晴らしい冒険を姫として伝えます。

        story: function() {
            return adventures[adventures.length - 1];
        }
    };
}

しかし、彼らが見るのは小さな女の子だけです...

var littleGirl = princess();

...魔法とファンタジーについての物語を伝える。

littleGirl.story();

そして大人たちは本当の王女を知っていたにもかかわらず、ユニコーンやドラゴンを決して信じることができなかったので、彼らは決して信じません。 大人たちは、彼らは幼い少女の想像の中にしか存在しないと言った。

しかし、私たちは本当の真実を知っています。 姫の中にいる少女が...

...本当に奥の小さな女の子がいる姫です。


6歳の年齢の人の答え(関数が何で、変数が何で、どのデータがあるかを知っていると仮定して)

関数はデータを返すことができます。関数から返すことのできるデータの1つは、別の関数です。その新しい関数が返されると、それを作成した関数で使用されているすべての変数と引数はなくならない。代わりに、その親関数は「閉じる」。言い換えれば、内部を調べて、返された関数を除いて使用された変数を見ることはできません。その新しい関数には、それを作成した関数の中を振り返り、その内部のデータを見る特別な能力があります。

function the_closure() {
  var x = 4;
  return function () {
    return x; // Here, we look back inside the_closure for the value of x
  }
}

var myFn = the_closure();
myFn(); //=> 4

それを説明するもう一つの本当に簡単な方法は、範囲の面でです:

大きなスコープの中に小さなスコープを作成するときはいつでも、小さなスコープは常により広いスコープにあるものを見ることができます。


初心者のためのJavaScriptクロージャ

2006年2月21日10時19分、MorrisによってTueに提出されました。 以来コミュニティ編集。

閉鎖は魔法ではない

このページでは、プログラマーがJavaScriptコードを使用してクロージャを理解できるようにクロージャについて説明します。 それは熟練したプログラマーや機能プログラマーのためではありません。

一度コアのコンセプトが完成すれば、クロージャーは理解するのが難しくありません 。 しかし、彼らは理論的または学問的な説明を読んで理解することは不可能です!

この記事は、メインストリーム言語でプログラミング経験を持ち、次のJavaScript関数を読むことができるプログラマーを対象としています。

function sayHello(name) {
  var text = 'Hello ' + name;
  var say = function() { console.log(text); }
  say();
}
sayHello('Joe');

2つの簡単な要約

  • 関数(foo)が他の関数(barとbaz)を宣言すると、関数が終了したときにfooで作成されたローカル変数のファミリは破棄されません 。 変数は単に外界に見えなくなるだけです。 したがって、Fooは関数バーとbazを巧みに返すことができ、誰もが耳を傾けることのできない閉鎖された一連の変数(「クロージャ」)を介して、互いに読み書きし、通信し続けることができます。将来は再びfoo。

  • クロージャはファーストクラスの関数をサポートする1つの方法です。 (最初に宣言されたとき)スコープ内の変数を参照したり、変数に割り当てたり、関数の引数として渡したり、関数の結果として返すことができる式です。

クロージャの例

次のコードは、関数への参照を返します。

function sayHello2(name) {
  var text = 'Hello ' + name; // Local variable
  var say = function() { console.log(text); }
  return say;
}
var say2 = sayHello2('Bob');
say2(); // logs "Hello Bob"

ほとんどのJavaScriptプログラマは、上記のコードで関数への参照がどのように変数( say2 )に返されるかを理解します。 そうしないと、閉鎖を学ぶ前にそれを見る必要があります。 Cを使っているプログラマは、その関数を関数へのポインタを返すものと考え、変数saysay2はそれぞれ関数へのポインタであると考えます。

関数へのCポインタと関数へのJavaScript参照には、大きな違いがあります。 JavaScriptでは、関数へのポインタとクロージャへの隠れたポインタの両方を持つ関数参照変数と考えることができます。

上記のコードには、匿名関数function() { console.log(text); } function() { console.log(text); }は、別の関数sayHello2()中で宣言されています。 JavaScriptでは、別の関数内でfunctionキーワードを使用すると、クロージャが作成されます。

Cやその他の共通言語で 、関数が返った 、スタックフレームが破棄されるため、すべてのローカル変数にアクセスできなくなります。

JavaScriptでは、別の関数内で関数を宣言すると、外部関数のローカル変数は、その関数から戻った後もアクセス可能なままです。 これは、 sayHello2()から返された後に関数say2()を呼び出すため、上で実証されています。 私たちが呼び出すコードは、関数sayHello2() ローカル変数である変数text参照していることに注意してください。

function() { console.log(text); } // Output of say2.toString();

say2.toString()出力を見ると、コードが可変textを参照していることがわかりtext 。 匿名関数は、 sayHello2()ローカル変数がクロージャー内に秘密に保持されているため、値'Hello Bob'を保持するtextを参照できます。

天才は、JavaScriptでは、関数参照には、作成されたクロージャーへの秘密の参照もあります。デリゲートがメソッドポインタとオブジェクトへのシークレット参照の仕方と似ています。

より多くの例

何らかの理由で、クロージャは、あなたがそれらについて読んだときに理解するのが本当に難しいようですが、いくつかの例を見ると、それらがどのように機能するかがはっきりしています。 私は、彼らがどのように動作するかを理解するまで、例を注意深く調べることをお勧めします。 クロージャの使い方を完全に理解していないクロージャを使い始めると、間もなく非常に奇妙なバグが作成されます。

例3

この例では、ローカル変数はコピーされず、参照によって保持されます。 これは、外部関数が存在しても、スタックフレームがメモリに保持されているかのようです。

function say667() {
  // Local variable that ends up within closure
  var num = 42;
  var say = function() { console.log(num); }
  num++;
  return say;
}
var sayNumber = say667();
sayNumber(); // logs 43

例4

3つのグローバル関数はすべて、 setupSomeGlobals()への一回の呼び出しですべてが宣言されるため、 同じクロージャに対する共通の参照を持ちます。

var gLogNumber, gIncreaseNumber, gSetNumber;
function setupSomeGlobals() {
  // Local variable that ends up within closure
  var num = 42;
  // Store some references to functions as global variables
  gLogNumber = function() { console.log(num); }
  gIncreaseNumber = function() { num++; }
  gSetNumber = function(x) { num = x; }
}

setupSomeGlobals();
gIncreaseNumber();
gLogNumber(); // 43
gSetNumber(5);
gLogNumber(); // 5

var oldLog = gLogNumber;

setupSomeGlobals();
gLogNumber(); // 42

oldLog() // 5

これらの3つの関数は、3つの関数が定義されたときのsetupSomeGlobals()ローカル変数と同じクロージャへのアクセスを共有しています。

上記の例では、 setupSomeGlobals()再度呼び出すと、新しいクロージャ(スタックフレーム!)が作成されます。 古いgLogNumbergIncreaseNumbergSetNumber変数は、新しいクロージャを持つ新しい関数で上書きされます。 (JavaScriptでは、関数を別の関数の中に宣言すると、その外部関数が呼び出されるたびに内部関数が再作成されます)。

実施例5

この例は、終了する前に外部関数内で宣言されたローカル変数がクロージャに含まれていることを示しています。 変数aliceは実際には無名関数の後に宣言されることに注意してください。 匿名関数が最初に宣言され、その関数が呼び出されると、 aliceが同じスコープ内にあるため(JavaScriptは可変ホイストします )、 alice変数にアクセスできます。 sayAlice()()は、 sayAlice()から返された関数参照を直接呼び出すだけです。これは、前に実行したのと同じですが、一時変数はありません。

function sayAlice() {
    var say = function() { console.log(alice); }
    // Local variable that ends up within closure
    var alice = 'Hello Alice';
    return say;
}
sayAlice()();// logs "Hello Alice"

Tricky: say変数もclosure内部にあり、 sayAlice()内で宣言されている可能性のある他の関数からアクセスできること、または内部関数内で再帰的にアクセスできることにも注意してください。

例6

これは多くの人々のための本当のつかまりです、あなたはそれを理解する必要があります。 ループ内で関数を定義する場合は非常に注意してください。クロージャのローカル変数は、最初に考えたように機能しない場合があります。

この例を理解するには、Javascriptの「可変ホイスト」機能を理解する必要があります。

function buildList(list) {
    var result = [];
    for (var i = 0; i < list.length; i++) {
        var item = 'item' + i;
        result.push( function() {console.log(item + ' ' + list[i])} );
    }
    return result;
}

function testList() {
    var fnlist = buildList([1,2,3]);
    // Using j only to help prevent confusion -- could use i.
    for (var j = 0; j < fnlist.length; j++) {
        fnlist[j]();
    }
}

 testList() //logs "item2 undefined" 3 times

result.push( function() {console.log(item + ' ' + list[i])}は、無名関数への参照を結果配列に3回追加します。それは好きです:

pointer = function() {console.log(item + ' ' + list[i])};
result.push(pointer);

この例を実行すると、 "item2 undefined"が3回記録されることに注意してください。 これは、前の例と同様に、 buildListresultiおよびitem )のローカル変数にはクロージャが1つしかないためです。 匿名関数がfnlist[j]()行で呼び出されたとき。 それらはすべて同じ単一クロージャを使用し、その1つのクロージャ内のiitem現在の値を使用します(ここで、ループが完了したため値3持ち、 item'item2'という値を持ちます)。 我々は0から索引付けしているので、 itemitem2値を持つことに注意してください。 そして、i ++はiを値3増やします。

varキーワードを使用して関数スコープの変数宣言の代わりに変数itemブロックレベルの宣言を使用すると( letキーワードを使用して)、何が起こるかを確認すると役立ちます。 その変更が行われた場合、配列result各無名関数には独自のクロージャがあります。 この例を実行すると、次のように出力されます。

item0 undefined
item1 undefined
item2 undefined

変数ivar代わりにletを使用let定義されている場合、出力は次のようになります。

item0 1
item1 2
item2 3

実施例7

この最後の例では、main関数を呼び出すたびに別々のクロージャが作成されます。

function newClosure(someNum, someRef) {
    // Local variables that end up within closure
    var num = someNum;
    var anArray = [1,2,3];
    var ref = someRef;
    return function(x) {
        num += x;
        anArray.push(num);
        console.log('num: ' + num +
            '; anArray: ' + anArray.toString() +
            '; ref.someVar: ' + ref.someVar + ';');
      }
}
obj = {someVar: 4};
fn1 = newClosure(4, obj);
fn2 = newClosure(5, obj);
fn1(1); // num: 5; anArray: 1,2,3,5; ref.someVar: 4;
fn2(1); // num: 6; anArray: 1,2,3,6; ref.someVar: 4;
obj.someVar++;
fn1(2); // num: 7; anArray: 1,2,3,5,7; ref.someVar: 5;
fn2(2); // num: 8; anArray: 1,2,3,6,8; ref.someVar: 5;

概要

すべてが完全にはっきりしていないと思われる場合は、例を使用して遊ぶのが最善の方法です。 説明を読むことは、例を理解するよりもはるかに困難です。 クロージャーやスタックフレームなどの説明は技術的には正しいものではありません。これらは理解を助けるための単純化されたものです。 基本的なアイデアがいったん完成すれば、後で詳細を取り上げることができます。

最終ポイント:

  • functionを別のfunction中で使うときはいつも、クロージャが使われます。
  • 関数内でeval()を使うと、常にクロージャが使われます。 eval('var foo = …')を使って新しいローカル変数を作成することもできますeval('var foo = …')
  • new Function(…)Functionコンストラクタ )を使用すると、クロージャは作成されません。 (新しい関数は外部関数のローカル変数を参照できません)。
  • JavaScriptのクロージャは、関数が終了したときと同じように、すべてのローカル変数のコピーを保持するようなものです。
  • クロージャは常に関数への単なるエントリとして作成され、ローカル変数はそのクロージャに追加されると考えるのがおそらく最善の方法です。
  • クロージャを持つ関数が呼び出されるたびに新しいローカル変数のセットが保持されます(関数内に関数宣言が含まれ、その内部関数への参照が返されるか、何らかの方法で外部参照が保持される)。
  • 2つの関数は同じソーステキストを持つように見えるかもしれませんが、 'hidden'クロージャのために全く異なる動作をします。 私はJavaScriptコードが実際に関数参照がクロージャを持っているかどうかを調べることはできないと思います。
  • myFunction = Function(myFunction.toString().replace(/Hello/,'Hola')); )などの動的ソースコードの変更をしようとしている場合、 myFunctionがクロージャであればmyFunctionませんもちろん、実行時にソースコードの文字列置換を行うことは決して考えませんが...)。
  • 関数宣言内で関数宣言を関数&mdash内で取得することは可能で、複数のレベルで終了することができます。
  • 私は通常、クロージャーはキャプチャされた変数と関数の両方の用語であると思います。 この記事ではその定義を使用していないことに注意してください。
  • 私は、JavaScriptのクロージャーは、関数型言語で通常見られるクロージャーとは異なると考えています。

リンク

ありがとう

あなたが閉鎖を学んだら(ここや他の場所で)、私はこの記事をより明確にする可能性のある変更についてあなたからのフィードバックに興味があります。 morrisjohns.com(morris_closure @)にメールを送ってください。 私はJavaScript上の教祖でもなく、閉鎖でもないことに注意してください。

Morrisのオリジナル投稿はInternet Archiveにあります。


クロージャは簡単です:

次の簡単な例は、JavaScriptクロージャの主なポイントをすべてカバーしています。 *

ここでは、加算と乗算が可能な計算機を生産する工場があります:

function make_calculator() {
  var n = 0; // this calculator stores a single number n
  return {
    add: function(a) {
      n += a;
      return n;
    },
    multiply: function(a) {
      n *= a;
      return n;
    }
  };
}

first_calculator = make_calculator();
second_calculator = make_calculator();

first_calculator.add(3); // returns 3
second_calculator.add(400); // returns 400

first_calculator.multiply(11); // returns 33
second_calculator.multiply(10); // returns 4000

要点:それぞれの呼び出しmake_calculatorは新しいローカル変数を作成しますn。このローカル変数は、その計算機addmultiply関数によって使用可能な状態になりmake_calculatorます。

スタックフレームに慣れている場合、これらの電卓は奇妙に見えます。返品nmake_calculatorにどうしてアクセスできますか?答えは、JavaScriptが「スタックフレーム」を使用せず、代わりに関数呼び出しの後に保持できる「ヒープフレーム」を使用すると想像することです。

インナーのような関数addmultiply外側の関数で宣言された変数にアクセスし、**は、と呼ばれている閉鎖

それは閉鎖にあるすべてのものです。


*例えば、変数が宣言される前に使用できることを単に示している例6を除いて、別の答えで与えられた "Closure for Dummies"記事のすべての点をカバーしています。引数がローカル変数(名前付き関数引数)にコピーされる点(1)と、(2)数値をコピーすると新しい数値が作成される点を除いて、受け入れられた回答のすべての点をカバーしますが、オブジェクト参照同じオブジェクトに別の参照を与えます。これらは知っていてもよいが、閉鎖とはまったく無関係である。また、この答えの例に非常に似ていますが、少し短くて抽象的ではありません。それはこの答えまたはこのコメントはJavaScriptであり、現在のプラグインあなたの内部関数へのループ変数の値: "プラグイン"ステップは、内部関数を囲み、各ループ反復で呼び出されるヘルパー関数でのみ行うことができます。 (厳密に言えば、内部関数はプラグインされたものではなく、変数のヘルパー関数のコピーにアクセスします。)また、クロージャを作成するときには非常に便利ですが、クロージャが何であるか、変数が記憶空間ではなく値にバインドされているMLのような関数言語では、クロージャが異なる方法で動作するため、追加の混乱があります。つまり、クロージャをある意味で理解している人のストリームを提供しています。変数が常に記憶領域にバインドされ、決して値にバインドされないJavaScriptでは正しくありません。

**いくつかのネストされた外部関数、またはグローバルな文脈であっても、この答えが明確に指摘している。


インタラクティブなJavaScriptチュートリアルをまとめ、クロージャの仕組みについて説明します。閉鎖とは何ですか?

ここに例の1つがあります:

var create = function (x) {
    var f = function () {
        return x; // We can refer to x here!
    };
    return f;
};
// 'create' takes one argument, creates a function

var g = create(42);
// g is a function that takes no arguments now

var y = g();
// y is 42 here

JavaScript関数は次のものにアクセスできます:

  1. 引数
  2. ローカル(ローカル変数とローカル関数)
  3. 環境には以下が含まれます:
    • DOMを含むグローバル
    • 外部機能のもの

関数がその環境にアクセスすると、その関数はクロージャになります。

外部関数は必須ではありませんが、ここでは説明しない利点があります。環境内のデータにアクセスすることにより、クロージャはそのデータを有効に保ちます。外部/内部関数のサブケースでは、外部関数がローカルデータを作成して終了することができますが、外部関数が終了しても内部関数が存続する場合、内部関数は外部関数のローカルデータを保持します生きている。

グローバル環境を使用するクロージャの例:

上の Vote UpイベントとVote Downボタンイベントは、グローバル変数isVotedUpとisVotedDownにアクセスできるクロージャーvoteUp_clickとvoteDown_clickとして実装されているとします。(簡単にするために、私はのQuestion Voteボタンを参照しています。Answer Voteボタンの配列ではありません)。

ユーザがVoteUpボタンをクリックすると、voteUp_click関数はisVotedDown == trueであるかどうかをチェックして投票するか、単に投票を取り消すかを決定します。関数voteUp_clickは、その環境にアクセスしているためクロージャです。

var isVotedUp = false;
var isVotedDown = false;

function voteUp_click() {
  if (isVotedUp)
    return;
  else if (isVotedDown)
    SetDownVote(false);
  else
    SetUpVote(true);
}

function voteDown_click() {
  if (isVotedDown)
    return;
  else if (isVotedUp)
    SetUpVote(false);
  else
    SetDownVote(true);
}

function SetUpVote(status) {
  isVotedUp = status;
  // Do some CSS stuff to Vote-Up button
}

function SetDownVote(status) {
  isVotedDown = status;
  // Do some CSS stuff to Vote-Down button
}

これらの4つの機能はすべて、すべて環境にアクセスするため、クロージャです。


あなたは眠りにつき、ダンを招待します。あなたはDanに1つのXBoxコントローラを持ってくるよう伝えます。

ダンはポールを招待します。ダンはポールにコントローラを1つ持って来るように頼みます パーティーに持ち込まれたコントローラーの数はいくつですか?

function sleepOver(howManyControllersToBring) {

    var numberOfDansControllers = howManyControllersToBring;

    return function danInvitedPaul(numberOfPaulsControllers) {
        var totalControllers = numberOfDansControllers + numberOfPaulsControllers;
        return totalControllers;
    }
}

var howManyControllersToBring = 1;

var inviteDan = sleepOver(howManyControllersToBring);

// The only reason Paul was invited is because Dan was invited. 
// So we set Paul's invitation = Dan's invitation.

var danInvitedPaul = inviteDan(howManyControllersToBring);

alert("There were " + danInvitedPaul + " controllers brought to the party.");

著者Closures、我々は彼らを必要とする理由を説明し、かなりよくクロージャを説明したもクロージャを理解するために必要であるLexicalEnvironmentを説明します。
ここに要約があります:

変数にアクセスしたがローカルではない場合はどうなりますか?ここのような:

この場合、インタプリタは外部LexicalEnvironmentオブジェクト内の変数を見つけます。

プロセスは2つのステップで構成されています。

  1. まず、関数fを作成すると、関数fは空の空間に作成されません。現在のLexicalEnvironmentオブジェクトがあります。上記の場合、ウィンドウです(関数作成時にはaは未定義です)。

関数が作成されると、現在のLexicalEnvironmentを参照する[[Scope]]という名前の隠しプロパティが取得されます。

変数が読み取られてもどこにも見つからない場合は、エラーが生成されます。

ネストされた関数

関数は、スコープチェーンとも呼ばれるLexicalEnvironmentsのチェーンを形成して、別のものの内部に入れ子にすることができます。

したがって、関数gはg、a、fにアクセスできます。

閉鎖

ネストされた関数は、外部関数が終了した後も存続することがあります。

LexicalEnvironmentsのマークアップ:

これはthis.sayユーザオブジェクト内のプロパティなので、ユーザが完了した後も引き続き存在します。

あなたが覚えていれば、いつthis.saythis.say.[[Scope]]いつものように)現在のLexicalEnvironmentへの内部参照を取得します。したがって、現在のユーザー実行のLexicalEnvironmentはメモリ内にとどまります。ユーザーのすべての変数もプロパティなので、慎重に保管され、通常はジャンク品ではありません。

全体のポイントは、内部関数が将来外部変数にアクセスしたい場合、その内部関数がそうすることができるようにすることです。

要約する:

  1. 内部関数は、外部LexicalEnvironmentへの参照を保持します。
  2. 内部関数は、外部関数が終了してもいつでも変数からアクセスすることができます。
  3. ブラウザは、LexicalEnvironmentとそのすべてのプロパティ(変数)を参照する内部関数が存在するまで、LexicalEnvironmentとそのすべてのプロパティ(変数)をメモリに保持します。

これはクロージャと呼ばれます。


なぜ答えがここで複雑であるのか分かりません。

ここに閉鎖があります:

var a = 42;

function b() { return a; }

はい。 あなたはおそらくそれを1日に数回使用します。


特定の問題に対処するためにクロージャが複雑な設計のハックだと考える理由はありません。いいえ、クロージャは、関数が宣言された(実行されていない)という観点から、より高いスコープから来る変数を使用することです。

今あなたができることは、もっと壮観になることができます、他の答えを見てください。


真剣に質問をしてみると、典型的な6歳の人が認知的にできることがわかるはずですが、JavaScriptに興味のある人はそれほど典型的ではありません。

幼児期の開発について:5〜7年それは言う:

あなたの子供は2段階の指示に従うことができます。 たとえば、子供に「キッチンに行ってゴミ箱を持ってきてください」と言ったら、その方向を思い出すことができます。

次のように、この例を使用してクロージャを説明することができます。

キッチンは、 trashBagsというローカル変数を持つクロージャです。 キッチンの中にgetTrashBagという関数があり、それは1つのゴミ箱を取得して返します。

これを次のようにJavaScriptで記述することができます:

function makeKitchen() {
  var trashBags = ['A', 'B', 'C']; // only 3 at first

  return {
    getTrashBag: function() {
      return trashBags.pop();
    }
  };
}

var kitchen = makeKitchen();

console.log(kitchen.getTrashBag()); // returns trash bag C
console.log(kitchen.getTrashBag()); // returns trash bag B
console.log(kitchen.getTrashBag()); // returns trash bag A

なぜクロージャが興味深いのかを説明するさらなるポイント:

  • makeKitchen()が呼び出されるtrashBags 、独自の別のtrashBagsで新しいクロージャーが作成されます。
  • trashBags変数は各キッチンの内部にローカルであり、外部からはアクセスできませんが、 getTrashBagプロパティの内部関数にはアクセスできます。
  • すべての関数呼び出しはクロージャを作成しますが、クロージャの内部にアクセスできる内部関数がクロージャの外側から呼び出されない限り、クロージャを保持する必要はありません。 getTrashBag関数でオブジェクトを返すと、ここでそのことが行われます。

閉鎖に関するWikipedia

コンピュータサイエンスでは、クロージャは、その関数の非局所的な名前(自由変数)の参照環境とともに関数です。

技術的には、JavaScriptでは、すべての関数はクロージャです。周囲のスコープで定義された変数には常にアクセスできます。

以来JavaScriptで範囲を規定する工事が関数ではなく、他の多くの言語のようにコードブロック、によって我々は通常、何を意味するの閉鎖 JavaScriptではありすでに実行周囲の関数で定義された非ローカル変数を扱う機能

クローズは、隠されたプライベートデータを持つ関数を作成するためによく使用されます(ただし、必ずしもそうではありません)。

var db = (function() {
    // Create a hidden object, which will hold the data
    // it's inaccessible from the outside.
    var data = {};

    // Make a function, which will provide some access to the data.
    return function(key, val) {
        if (val === undefined) { return data[key] } // Get
        else { return data[key] = val } // Set
    }
    // We are calling the anonymous surrounding function,
    // returning the above inner function, which is a closure.
})();

db('x')    // -> undefined
db('x', 1) // Set x to 1
db('x')    // -> 1
// It's impossible to access the data object itself.
// We are able to get or set individual it.

ems

上記の例では、一度実行された無名関数を使用しています。しかし、そうである必要はありません。それmkdbは、呼び出されるたびにデータベース関数を生成し、後で実行され、実行されます(例:)。生成されるすべての関数は、独自の隠しデータベースオブジェクトを持ちます。クロージャの別の使用例は、関数を返さないときですが、目的ごとに複数の関数を含み、それぞれが同じデータにアクセスできるオブジェクトです。


クロージャは、内部関数が外部関数内の変数にアクセスできる場所です。おそらく、閉鎖のために得ることができる最も簡単な一行の説明です。


すでに多くの解決策があることはわかっていますが、この小さくてシンプルなスクリプトがこのコンセプトを実証するのに役立つと思います。

// makeSequencer will return a "sequencer" function
var makeSequencer = function() {
    var _count = 0; // not accessible outside this function
    var sequencer = function () {
        return _count++;
    }
    return sequencer;
}

var fnext = makeSequencer();
var v0 = fnext();     // v0 = 0;
var v1 = fnext();     // v1 = 1;
var vz = fnext._count // vz = undefined

ストローマン

私はボタンがクリックされた回数を知り、3回目のクリックごとに何かをする必要があります...

かなり明白な解決策

// Declare counter outside event handler's scope
var counter = 0;
var element = document.getElementById('button');

element.addEventListener("click", function() {
  // Increment outside counter
  counter++;

  if (counter === 3) {
    // Do something every third time
    console.log("Third time's the charm!");

    // Reset counter
    counter = 0;
  }
});
<button id="button">Click Me!</button>

これはうまくいくでしょうが、変数を追加することで外側のスコープに侵入します。その目的はカウントを追跡することです。 場合によっては、外部アプリケーションがこの情報にアクセスする必要がある場合があるため、これが望ましい場合もあります。 しかしこの場合、3回目のクリックごとの動作を変更するだけなので、 この機能をイベントハンドラ内囲むことをお勧めします

このオプションを検討する

var element = document.getElementById('button');

element.addEventListener("click", (function() {
  // init the count to 0
  var count = 0;

  return function(e) { // <- This function becomes the click handler
    count++; //    and will retain access to the above `count`

    if (count === 3) {
      // Do something every third time
      console.log("Third time's the charm!");

      //Reset counter
      count = 0;
    }
  };
})());
<button id="button">Click Me!</button>

ここにいくつか注意してください。

上記の例では、JavaScriptのクロージャー動作を使用しています。 この動作により、任意の関数が作成されたスコープに無期限にアクセスできるようになります。 実際にこれを適用するには、直ちに別の関数を返す関数を呼び出し、返す関数は(前述のクロージャの振る舞いのために)内部のcount変数にアクセスできるため、結果として使用するためのプライベートスコープとなります機能...それほど単純ではない? それを薄くしましょう...

シンプルなワンラインクロージャー

//          _______________________Immediately invoked______________________
//         |                                                                |
//         |        Scope retained for use      ___Returned as the____      |
//         |       only by returned function   |    value of func     |     |
//         |             |            |        |                      |     |
//         v             v            v        v                      v     v
var func = (function() { var a = 'val'; return function() { alert(a); }; })();

返された関数の外にあるすべての変数は、返された関数で使用できますが、返された関数オブジェクトでは直接利用できません。

func();  // Alerts "val"
func.a;  // Undefined

それを得る? したがって、主な例では、count変数はクロージャー内に含まれ、イベントハンドラで常に使用できるため、クリックからクリックまでステートを保持します。

また、このプライベート変数状態は、読み込みとプライベートスコープ変数への割り当ての両方に完全にアクセス可能です。

そこに行く。 この動作を完全にカプセル化しています。

完全なブログ投稿 (jQueryの考慮事項を含む)


JavaScriptを有効にする方法については「JavaScriptの設定方法」をご覧ください。

クロージャは、別の関数のスコープ(その変数と関数)にアクセスできる関数です。クロージャを作成する最も簡単な方法は、関数内の関数を使用することです。その理由は、JavaScriptでは関数が常にその関数のスコープにアクセスできるからです。

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        alert(outerVar);
    }
    
    innerFunction();
}

outerFunction();

アラート:猿

上記の例では、innerFunctionを呼び出すouterFunctionが呼び出されています。outerVarがinnerFunctionでどのように使用可能であるかに注目してください。これは、outerVarの値を正しく警告することによって証明されます。

次に、以下を考慮してください。

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        return outerVar;
    }
    
    return innerFunction;
}

var referenceToInnerFunction = outerFunction();
alert(referenceToInnerFunction());

アラート:猿

referenceToInnerFunctionはouterFunction()に設定され、単にinnerFunctionへの参照を返します。referenceToInnerFunctionが呼び出されると、outerVarが返されます。上記と同様に、これは、innerFunctionがouterFunctionの変数であるouterVarにアクセスできることを示しています。さらに、outerFunctionの実行が終了した後でも、このアクセスを保持することに注意するのは興味深いことです。

物事が本当に面白くなるところです。outerFunctionを取り除く、つまりnullに設定すると、referenceToInnerFunctionがouterVarの値へのアクセスを失うと思われるかもしれません。しかし、そうではありません。

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        return outerVar;
    }
    
    return innerFunction;
}

var referenceToInnerFunction = outerFunction();
alert(referenceToInnerFunction());

outerFunction = null;
alert(referenceToInnerFunction());

ALERT:猿ALERT:猿

しかし、これはどうですか?outerFunctionがnullに設定されたので、referenceToInnerFunctionはまだouterVarの値を知ることができますか?

innerFunctionがouterFunctionの内部に配置されてclosureが最初に作成されたとき、innerFunctionはouterFunctionのスコープ(その変数と関数)への参照をそのスコープチェーンに追加したため、referenceToInnerFunctionが依然としてouterVarの値にアクセスできる理由があります。つまり、innerFunctionには、outerVarを含むすべてのouterFunctionの変数へのポインタまたは参照があります。したがって、outerFunctionの実行が終了した場合、またはnullに設定された場合でも、outerVarのようなスコープ内の変数は、返された内部関数の未処理の参照によってメモリ内に留まります。 referenceToInnerFunction。 outerVarと残りのouterFunctionの変数をメモリから本当に解放するには、この未処理参照を取り除く必要があります。また、referenceToInnerFunctionをnullに設定するといいでしょう。

//////////

注意すべき閉鎖についての2つの他の事柄。第1に、クロージャは常にその包含関数の最後の値にアクセスできます。

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        alert(outerVar);
    }
    
    outerVar = "gorilla";

    innerFunction();
}

outerFunction();

アラート:ゴリラ

第2に、クロージャが作成されると、そのクロージャの関数の変数と関数のすべてへの参照が保持されます。選んで選ぶことはできません。しかし、閉鎖は、メモリを大量に消費する可能性があるため、慎重に、または少なくとも注意深く使用する必要があります。多くの変数は、包含する関数の実行が終了してから長い間、メモリに保持されます。


クロージャは、誰もが直感的に動作すると予想されるいくつかの動作作業を行うために使用されるため、説明するのは難しいです。 私はそれらを説明する最善の方法を見つけます(そして、彼らが何をするか学ぶ方法)は、それらがない状況を想像することです:

    var bind = function(x) {
        return function(y) { return x + y; };
    }
    
    var plus5 = bind(5);
    console.log(plus5(3));

JavaScript クロージャを知らない場合はどうなりますか? 最後の行の呼び出しをメソッド本体(これは基本的に関数呼び出しが行うものです)に置き換えれば、次のようになります:

console.log(x + 3);

ここで、 xの定義はどこですか? 現在の範囲では定義しませんでした。 唯一の解決策は、 plus5範囲(または親の範囲)を指定させることです。 このようにして、 xは明確に定義され、値5にバインドされます。


私はそれを6歳の子供に説明します:

大人が家を所有する方法を知っていて、彼らはそれを家に呼びますか?母親に子供がいるとき、子供は本当に何も所有していませんよね?しかし、両親は家を所有しているので、誰かが子供に「あなたの家はどこですか?」と尋ねると、「その家!」と答えることができ、両親の家を指すことができます。「閉鎖」とは、たとえそれが本当に親を所有しているにもかかわらず、(たとえ海外であっても)子供が常に家を持っていると言うことができる能力です。


dlaliberteによる最初のポイントの例:

クロージャーは、内部関数を返すときに作成されるだけではありません。実際、囲み関数はまったく返す必要はありません。代わりに、内側の関数を外側のスコープ内の変数に代入したり、引数としてすぐに使用できる別の関数に渡したりすることもできます。したがって、内部関数が呼び出されるとすぐに内部関数にアクセスできるため、内部関数のクロージャは、内部関数が呼び出された時点ですでに存在している可能性があります。

var i;
function foo(x) {
    var tmp = 3;
    i = function (y) {
        console.log(x + y + (++tmp));
    }
}
foo(2);
i(3);

OK、6歳のファンが閉鎖します。あなたは閉鎖の最も単純な例を聞きたいですか?

次の状況を想像してみましょう:運転手が車に座っています。その車は飛行機の中にあります。飛行機が空港にあります。彼の車の外にあるが、飛行機の内部にいる物にアクセスする能力は、たとえその飛行機が空港を離れるとしても閉鎖である。それでおしまい。あなたが27歳になったら、より詳細な説明を見てください。

私の飛行機物語をコードに変換する方法は次のとおりです。

var plane = function(defaultAirport) {

  var lastAirportLeft = defaultAirport;

  var car = {
    driver: {
      startAccessPlaneInfo: function() {
        setInterval(function() {
          console.log("Last airport was " + lastAirportLeft);
        }, 2000);
      }
    }
  };
  car.driver.startAccessPlaneInfo();

  return {
    leaveTheAirport: function(airPortName) {
      lastAirportLeft = airPortName;
    }
  }
}("Boryspil International Airport");

plane.leaveTheAirport("John F. Kennedy");


別の関数内でfunctionキーワードを見ると、内部関数は外部関数の変数にアクセスできます。

function foo(x) {
  var tmp = 3;

  function bar(y) {
    console.log(x + y + (++tmp)); // will log 16
  }

  bar(10);
}

foo(2);

barfooへの引数として定義されたxアクセスでき、 fooからもtmpアクセスできるので、これは常に16を記録します。

それ閉鎖です。 クロージャと呼ばれるために関数を返す必要はありません。 直近のレキシカルスコープの外にある変数にアクセスするだけで、クロージャーが作成されます。

function foo(x) {
  var tmp = 3;

  return function (y) {
    console.log(x + y + (++tmp)); // will also log 16
  }
}

var bar = foo(2); // bar is now a closure.
bar(10);

上の関数は、もはやスコープの内部にはなくても、 barはまだxtmp参照できるので、16を記録します。

しかし、 tmpはまだbarのクロージャの内側にぶら下がっているので、それも増分されています。 これは、あなたがbarを呼び出すたびに増分されbar

クロージャの最も簡単な例は次のとおりです。

var a = 10;

function test() {
  console.log(a); // will output 10
  console.log(b); // will output 6
}
var b = 6;
test();

JavaScript関数が呼び出されると、新しい実行コンテキストが作成されます。 この実行コンテキストは、関数の引数と親オブジェクトとともに、その外部で宣言されたすべての変数を受け取ります(上記の例では、 'a'と 'b'の両方)。

複数のクロージャ関数を作成するには、それらの関数のリストを返すか、グローバル変数に設定します。 これらはすべて同じ xと同じtmpを参照しますが、それらは独自のコピーを作成しません。

ここで、番号xはリテラル番号です。 JavaScriptの他のリテラルと同様に、 fooが呼び出されると、 xが引数xとしてfoo コピーされます。

一方、JavaScriptは、オブジェクトを扱うときに常に参照を使用します。 たとえば、 fooをオブジェクトで呼び出すと、それが返すクロージャは元のオブジェクトを参照します。

function foo(x) {
  var tmp = 3;

  return function (y) {
    console.log(x + y + tmp);
    x.memb = x.memb ? x.memb + 1 : 1;
    console.log(x.memb);
  }
}

var age = new Number(2);
var bar = foo(age); // bar is now a closure referencing age.
bar(10);

予想どおり、 bar(10)x.membが増えます。 予想できないことは、 xage変数と同じオブジェクトを単に参照していることです。 barに2 age.membコールした後、 age.membは2になります! この参照は、HTMLオブジェクトのメモリリークの基礎となります。


子供たちは、両親がいなくても、彼らが両親と分かち合った秘密をいつも覚えています。これはクロージャが関数のためのものです。

JavaScript関数の秘密はプライベート変数です

var parent = function() {
 var name = "Mary"; // secret
}

呼び出すたびに、ローカル変数 "name"が作成され、 "Mary"という名前が付けられます。関数が終了するたびに変数が失われ、名前は忘れられます。

あなたが推測しているように、関数が呼び出されるたびに変数が再作成され、誰もそれらを知ることはないので、変数が格納される秘密の場所が必要です。これは秘密の部屋と呼ばれることもありますし、スタックローカルスコープと呼ばれることもありますが、それは問題ではありません。私たちは彼らがどこかに存在し、記憶に隠されていることを知っています。

しかし、JavaScriptでは、他の関数の内部で作成された関数、親のローカル変数を知り、生存している限り、それらを保持するという非常に特殊なことがあります。

var parent = function() {
  var name = "Mary";
  var child = function(childName) {
    // I can also see that "name" is "Mary"
  }
}

したがって、私たちが親関数にいる限り、秘密変数を秘密の場所から共有する1つ以上の子関数を作成することができます。

しかし、悲しいことは、もし子供が親の機能の私的な変数でもあれば、親が終わると死ぬだろう、そして秘密は彼らと共に死ぬだろうということです。

生きるために、子供は遅刻する前に出なければならない

var parent = function() {
  var name = "Mary";
  var child = function(childName) {
    return "My name is " + childName  +", child of " + name; 
  }
  return child; // child leaves the parent ->
}
var child = parent(); // < - and here it is outside 

そして今、メアリーは「もはや走っていなくても」、彼女の記憶は失われず、彼女の子供はいつも彼女の名前と一緒に一緒に共有した他の秘密を覚えています。

あなたが子供を「アリス」と呼ぶと、彼女は答えます

child("Alice") => "My name is Alice, child of Mary"

それがすべてです。


私はGOOD / BADの比較でよりよく学ぶ傾向があります。私は作業コードの後に​​誰かが遭遇する可能性のある非作業コードを見ているのが好きです。私は比較を行い、私が思いつくことができる最も単純な説明に違いを沸かせようとするjsFiddleをまとめまし

閉鎖は正しく行われました:

console.log('CLOSURES DONE RIGHT');

var arr = [];

function createClosure(n) {
    return function () {
        return 'n = ' + n;
    }
}

for (var index = 0; index < 10; index++) {
    arr[index] = createClosure(index);
}

for (var index in arr) {
    console.log(arr[index]());
}
  • 上記のコードでcreateClosure(n)は、ループのすべての反復で呼び出されます。私は、変数の名前ことに注意してくださいn、それがあることを強調するために、新たな新機能のスコープで作成した変数と同じ変数ではないindex外側のスコープにバインドされています。

  • これにより、新しいスコープが作成nされ、そのスコープにバインドされます。つまり、反復ごとに10個の別々のスコープがあります。

  • createClosure(n) そのスコープ内のnを返す関数を返します。

  • 各スコープ内nでは、createClosure(n)呼び出された時の値にバインドされるので、返されるネストされた関数は、呼び出されnた時の値を常に返しcreateClosure(n)ます。

閉鎖は間違っていた:

console.log('CLOSURES DONE WRONG');

function createClosureArray() {
    var badArr = [];

    for (var index = 0; index < 10; index++) {
        badArr[index] = function () {
            return 'n = ' + index;
        };
    }
    return badArr;
}

var badArr = createClosureArray();

for (var index in badArr) {
    console.log(badArr[index]());
}
  • 上記のコードでは、ループはcreateClosureArray()関数内で移動され、関数は完成した配列を返します。これは一見一見直観的です。

  • 明白でないかもしれないことcreateClosureArray()は、ループの各反復ごとに1つではなく、この関数のために1つのスコープが作成された後にのみ呼び出されるということです。

  • この関数内でindexは、名前付き変数が定義されています。ループが実行され、返される配列に関数が追加されますindex。1回だけ呼び出される関数index内で定義されていることに注意してくださいcreateClosureArray

  • createClosureArray()関数内にスコープが1つしかないため、そのスコープ内のindex値にのみバインドされます。言い換えると、ループが値を変更するたびに、indexそのスコープ内でループを参照するすべての値に対してループが変更されます。

  • 配列に追加されたすべての関数indexは、最初の例のように10種類のスコープから10種類の変数の代わりに、定義された親スコープからSAME 変数を返します。最終的には、10個の関数すべてが同じスコープから同じ変数を返します。

  • ループが終了し、index変更が完了した後、終了値は10であったため、配列に追加されたすべての関数indexは、現在10に設定されている単一変数の値を返します。

結果

閉鎖完了右
n = 0
n = 1
n = 2
n = 3
n = 4
n = 5
n = 6
n = 7
n = 8
n = 9

閉鎖は間違っていた
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10


JavaScript

 var str = "My big string contain apples and oranges";
 var n = str.indexOf("apples"); 
 alert(n); //will alert 22, -1 if not found

jQuery

  <p>My big string contain apples and oranges</p>
  alert($("p:contains(apples)")[0] != undefined); //will alert true if found




javascript function variables scope closures