2

多くの読書とハッキングの後、ようやく JavaScript クロージャーとその使用法を理解し始めたように感じます。しかし、私が読んだリソースのいくつかは、私には少し矛盾しているように見える方法で物事を表現しています. または、多分私はそれらを読みすぎているだけです.

helephant.com の優れた記事 (リンク) には、次のように記載されています。

クロージャーは、別の関数内にネストされた関数がその親のスコープから変数にアクセスするときに作成されます。

と...

クロージャーは、内部関数が作成されたときではなく、外部関数が終了したときに実際に作成されます。

与えられた例のコンテキストで、これらの点の両方を理解しています。

ただし、John Resig のサイト (リンク)のより基本的なチュートリアルでは、以下のコード ブロックにはクロージャーが含まれていると述べられています。

var num = 10;

function addNum(myNum){
  return num + myNum;
}

addNum(5);

私が見たクロージャのほとんどの有用な例は、内部関数への参照を返し、後でそれを使って何かをしますしたがって、この例は無意味に思えますが、とにかく、それが依然としてクロージャであることを受け入れて理解しようとしましょう。しかし、helephant の概念 (外部関数が終了するまでクロージャーは作成されない) を統合して少しハックしようとすると、次のようになります。

function outerFunction() {
    var num = 10;

    function addNum(myNum){
      return num + myNum;
    }

    alert(addNum(0));

    num = 5;
}

outerFunction();

現在、Resig によるとaddNum、クロージャーが作成されます。helephant によると、クロージャーはouterFunction返されるまで作成されません。ただし...クロージャー(実際にクロージャーである場合)は、終了時にnumfrom beforeouterFunctionの値を使用します。矛盾?

確かに、終了する前に呼び出し たので... の現在の値を使用することは論理的に思えます。しかし、これは、彼が提示した単純な例が実際に閉鎖であるというResigの声明に疑問を投げかけます. しかし... レシグに質問する私は誰ですか? きっと私は何かを誤解していますか?addNumouterFunctionnum

これを Q&A 形式によりよく適合させるために、ここで私の質問を要約します。

(1) Resig の例 (およびさらに下にある私の拡張機能) はクロージャーですか?

(2) はいの場合、外側のスコープが戻る前に囲まれた var の値を使用するのはなぜですか?

4

5 に答える 5

2

したがって、これはセマンティクスについて口論するほどの矛盾ではありません。状況をかなりよく理解しているようです。クロージャーの仕組みに関する説明は正確に見えます。

ウィキペディアのエントリから(元は Sussman と Steele による。「Scheme: An interpreter for extended lambda calculus」.)

コンピューター サイエンスでは、クロージャー (レキシカル クロージャーまたは関数クロージャーとも呼ばれます) は、参照環境 (その関数の非ローカル変数 (自由変数とも呼ばれます) への参照を格納するテーブル) を伴う関数または関数への参照です。 . 単純な関数ポインターとは異なり、クロージャーを使用すると、直接のレキシカル スコープの外で呼び出された場合でも、関数は非ローカル変数にアクセスできます。

したがって、クロージャーは技術的には、参照環境 (javascript の外部関数スコープへの参照) を持つ単なる関数です。しかし、それを特別なものにしているのは、別のスコープから呼び出すことができるということです。

したがって、技術的には Resig の例はクロージャーです。外部環境を参照する機能です。その外側の環境はたまたまグローバル スコープですが、まだグローバル スコープがあります。しかし、それが通過するまで、他の関数と比較して区別/特別ではありません。

最後に、これらの例のいずれかをクロージャーと呼ぶことは間違いではありません。しかし、それらがジェネリック関数と比較して有用であるためには、呼び出しコンテキストから渡したいと思うでしょう。

于 2013-03-29T04:06:05.307 に答える
1

window実際には、それ自体に関数スコープがあります。

windowは、関数スコープ ( var bob = "Bob";) とオブジェクト プロパティ ( window.bob = "Bob";) の両方で存在します。通常の使用法では同じですが、実際には違います。

window.bob = "Bob"
delete window.bob;
window.bob; // undefined

var bob = "Bob"
delete window.bob;
bob; // "Bob"

したがって、実行中のスコープが外部関数のスコープにアクセスできる場合はいつでもクロージャが発生します。

しかしwindow、クロージャーを提供するとは言わない理由windowグローバルにアクセスできるからです。
つまり、サイト上のすべてのコードはwindowの関数スコープにアクセスできます。
したがって、アクセスを妨げていないため、実際には「囲まれている」わけではありません。

これからわか​​ることは、グローバルスコープに依存する例は、クロージャーが構築されているのと同じ手法であり、したがって、それらを把握し始める最も簡単で最も一般的な方法であるということです...

...しかし、これらの値は外部の世界に隠されているわけではないため、実際には (外部関数が戻ることによって) 実際の閉鎖は発生しません。

于 2013-03-29T04:06:21.357 に答える
1

したがって、クロージャーで示されているポイントは、外側の関数のスコープ外であっても、内側の関数が外側の関数の変数にしがみつくことができるということです。

内側の関数は、Javascript のプロトタイプの継承を介して外側の関数の変数と値のコピーを継承し、2 番目の変数の割り当ては外側の関数のスコープでまだ評価されていないため、内側の関数は現在割り当てられている値のコピーを継承します。

この動作の良いショーケース:

http://jsfiddle.net/JD32X/

function outer()
{
    var test = "hello";
    var blah = function() { window.alert(test); };
    blah();
    test = "not hello";
    blah();
}
outer();
于 2013-03-29T04:09:38.000 に答える
0

ベン・マコーミックは正しいです。彼の答えを拡張し、彼が書いたことが象の記事にどのように当てはまるかを説明したいだけです (ただし、コメントを追加するのに十分な評判ポイントがありません)。

象がそのコメントを書いたとき、彼は特定の点を指摘していました. 私自身の例を使用して同じ点を指摘するために、次のコード サンプルを検討してください。ループして 10 個の関数を作成し、それらを配列に入れ、配列をループして呼び出します。

function getFunctions() {
    var functions = [];
    for (var i=0; i < 10; i++) {
        functions.push(function() { window.alert(i); });
    }
    return functions;
}
functionArray = getFunctions();
for (var j=0; j < functionArray.length; j++) {
    var f = functionArray[j];
    f();
}

Helephant は混乱しないように警告しています: これは数字の "10" を 10 回警告します。1、2、3、... 10 には警告しません。しかし、彼の用語と説明は完全に正確ではありません。がfunction() { window.alert(i); }実行されると、システムはクロージャを作成します。Ben McCormick が述べているように、クロージャーは 2 つのポインターで構成されています。1 つは関数定義 (関数のパラメーターと関数本体) を指します。もう 1 つは環境を指します。この例では、10 個のクロージャーすべてが同じ環境を指しています。

繰り返しますが、技術的に正確に言うと、10 個のクロージャがありますが、環境は 1 つだけです。この環境は、i「10」にバインドされている場所です。これは直感的に理解できるはずです。 var iは 1 回だけ実行されるため、 のインスタンスは 1 つだけですifunction() { window.alert(i); }は 10 回実行されるため、10 個のクロージャーがあります。しかし、10 個のクロージャーはすべて同じ環境を指しています。

于 2016-10-19T02:44:08.630 に答える
0

@DaveJohnson、こんにちは。The closure is actually created when the outer function exits, not when the inner function is createdが真のステートメントである理由を説明してみましょう。

あるスコープ (関数) 内で新しい変数を定義すると、この変数がlivesこのスコープ内で作成されます。関数が戻ると、通常、その変数への参照は保持されないため、存在しなくなります。「ガベージコレクション」になります。ただし、一部の内部関数 (スコープ) がこの変数を使用し、外部スコープが戻った後も参照を保持している場合、クロージャーが作成されます。したがって、The closure is actually created when the outer function exits, not when the inner function is created.

あなたの例では:

function outerFunction() {
    var num = 10;
    function addNum(myNum){
        return num + myNum;
    }
    alert(addNum(0));
    num = 5;
}
outerFunction();

num 変数は、outerFunction を呼び出すときに作成され、関数が戻るときに完全に解放されます。なんで?その後、そのオブジェクトへの参照は保持されないためです。実際には、内部関数 addNum もそのスコープで作成され、outerFunction が戻った後は存在しません。したがって、ここでは本当の閉鎖は見られません

編集: ここに表示されているのは、単なるSCOPING. はい - addNum 関数内で、外部スコープから変数への参照が行われますが、その変数はenclosed. たとえば、コードのその場所でenclosedaddNum 関数をイベント ハンドラーとしてボタン クリック イベントに割り当てた場合です。このように、オブジェクトとしての関数自体は、outerFunction が返された後も存在し続けます。これは、その関数への参照がボタン クリック イベントに存在し、その場合、num変数への参照も存在するためです。


そのように addNum 関数を作成した場合:

...
window.addNum = function(myNum) {
     return num + myNum;
}
...

それはまったく別のケースです。ここでクロージャが作成されますが、これは外側の関数が を返すときに発生すると言えます。なんで?numオブジェクトへの参照がまだあるため、それは変数が「ガベージコレクト」されるべき 瞬間だからです。

閉鎖とは何かについてのかなり良い説明があります。あなたの外側の範囲を結婚として想像してみてください。このスコープは、実際には家族と一緒に住む家です。妻、2 人の子供、家具などがあります。それらはすべて、結婚の「スコープ」内で作成されたプロパティと機能です。あなたが貪欲になると、家から追い出されます(機能「結婚」が戻ってきました)が、あなたはまだあなたの結婚内で作成されたオブジェクトへの参照を保持しており、これらのオブジェクトはあなたの2人の子供になります:)。あなたの妻については、妻としてあなた自身の「スコープ」にはもう存在しません。あなたの家具はもはやあなたのものではありません:)

更新
最初に説明するつもりだった'The closure is actually created when the outer function exits, not when the inner function is createdのは、あなたの例の ' は addNum 関数に適用されますが、outerFunction には適用されないということです。

UPDATE 2 - 値を囲むことではなく、参照を囲むことです

// man is not the value "Jonh Resig"
// it's a pointer to SOME 'man', existing in the memory
var man = "John Resig";

function haveSex(woman) {
     return man + woman; // creating a baby;
}
haveSex("Some Girl");      // baby of "Some Girl" and Resig
man = "Douglas Crockford"; // changing `man` reference to point to another object
haveSex("Some Girl");      // baby of "Some Girl" and Crockford
于 2013-03-29T09:37:10.380 に答える