5

このコードは、クリックしたときに画像の番号を含むアラートをポップアップ表示することになっています。

for(var i=0; i<10; i++) {
    $("#img" + i).click(
        function () { alert(i); }
    );
}

http://jsfiddle.net/upFaJ/で動作していないことがわかります。これは、すべてのクリック ハンドラー クロージャーが同じ object を参照しているためであることがわかっているため、すべてのハンドラーがiトリガーされると「10」がポップアップします。

ただし、これを行うと、正常に動作します。

for(var i=0; i<10; i++) {
    (function (i2) {
        $("#img" + i2).click(
            function () { alert(i2); }
        );
    })(i);
}

http://jsfiddle.net/v4sSD/で動作していることがわかります。

なぜそれが機能するのですか?iメモリにはまだ 1 つのオブジェクトしかありませんよね? オブジェクトは常にコピーではなく参照によって渡されるため、自己実行関数呼び出しは違いを生じさせません。2 つのコード スニペットの出力は同一である必要があります。iでは、オブジェクトが 10 回コピーされるのはなぜでしょうか。なぜそれが機能するのですか?

このバージョンが動作しないのは興味深いと思います:

for(var i=0; i<10; i++) {
    (function () {
        $("#img" + i).click(
            function () { alert(i); }
        );
    })();
}

オブジェクトを関数パラメーターとして渡すと、すべての違いが生じるようです。


i編集: OK、前の例は、関数呼び出しに値で渡されるプリミティブ ( ) によって説明できます。しかし、実際のオブジェクトを使用するこの例はどうでしょうか?

for(var i=0; i<5; i++) {
    var toggler = $("<img/>", { "src": "http://www.famfamfam.com/lab/icons/silk/icons/cross.png" });
    toggler.click(function () { toggler.attr("src", "http://www.famfamfam.com/lab/icons/silk/icons/tick.png"); });
    $("#container").append(toggler);
}

動作しない: http://jsfiddle.net/Zpwku/

for(var i=0; i<5; i++) {
    var toggler = $("<img/>", { "src": "http://www.famfamfam.com/lab/icons/silk/icons/cross.png" });
    (function (t) {
        t.click(function () { t.attr("src", "http://www.famfamfam.com/lab/icons/silk/icons/tick.png"); });
        $("#container").append(t);
    })(toggler);
}

作業: http://jsfiddle.net/YLSn6/

4

7 に答える 7

8

オブジェクトを関数パラメーターとして渡すとクロージャーが壊れ、ループ内から関数に代入できるという点で、ほとんどの答えは正しいです。しかし、なぜこれが当てはまるのかを指摘したいと思います。これは単なる閉鎖の特殊なケースではありません。

ご覧のとおり、javascript がパラメーターを関数に渡す方法は、他の言語とは少し異なります。まず、プリミティブ値またはオブジェクトである天候に応じて、2 つの方法があるようです。プリミティブ値の場合は値渡しのように見え、オブジェクトの場合は参照渡しのようです。

JavaScript が関数の引数を渡す方法

実際、javascript が何をするかについての本当の説明は、両方の状況と、それがクロージャーを壊す理由を、たった 1 つのメカニズムを使用して説明しています。

JavaScriptが実際に行うことは、参照のコピーによってパラメーターを渡すことです。つまり、パラメーターへの別の参照を作成し、その新しい参照を関数に渡します。

値渡し?

JavaScript のすべての変数が参照であると仮定します。他の言語では、変数が参照であると言うとき、次のように動作すると予想されます。

var i = 1;
function increment (n) { n = n+1 };
increment(i); // we would expect i to be 2 if i is a reference

しかし、javascript では、そうではありません。

console.log(i); // i is still 1

それは古典的な値渡しですよね?

参照渡し?

しかし、オブジェクトの場合は別の話です。

var o = {a:1,b:2}
function foo (x) {
    x.c = 3;
}
foo(o);

パラメータが値で渡された場合、oオブジェクトは変更されていないと予想されますが、次のようになります。

console.log(o); // outputs {a:1,b:2,c:3}

それは古典的な参照渡しです。したがって、天気に応じて 2 つの動作があり、プリミティブ型またはオブジェクトを渡します。

待って、何?

しかし、ちょっと待って、これをチェックしてください:

var o = {a:1,b:2,c:3}
function bar (x) {
    x = {a:2,b:4,c:6}
}
bar(o);

何が起こるか見てみましょう:

console.log(o); // outputs {a:1,b:2,c:3}

何!それは参照渡しではありません!値は変更されません。

これが、私がこれを pass by copy of referenceと呼ぶ理由です。このように考えれば、すべてが理にかなっています。オブジェクトは同じように動作するため、関数に渡されたときにプリミティブが特別な動作を持つと考える必要はありません。変数が指すオブジェクトを変更しようとすると、参照渡しのように機能しますが、参照自体を変更しようとすると、値渡しのように機能します。

これは、変数を関数パラメーターとして渡すことによってクロージャーが壊れる理由も説明しています。関数呼び出しは、元の変数のようにクロージャーによってバインドされていない別の参照を作成するためです。

エピローグ:嘘をついた

これを終わらせる前にもう一つ。これにより、プリミティブ型とオブジェクトの動作が統一されると前に述べました。実際には違います。プリミティブ型はまだ異なります。

var i = 1;
function bat (n) { n.hello = 'world' };
bat(i);
console.log(i.hello); // undefined, i is unchanged

あきらめる。これでは意味がありません。それはまさにその通りです。

于 2012-11-22T08:37:53.207 に答える
3

これは、関数を呼び出してを渡しているためです。

for (var i = 0; i < 10; i++) {
    alert(i);
}

これが異なる値を警告することを期待していますよね? toの現在の値をi渡しているためですalert

function attachClick(val) {
    $("#img" + val).click(
        function () { alert(val); }
    );
}

この関数を使用するvalと、渡されたものは何でも警告することが期待されますよね? これは、ループで呼び出す場合にも機能します。

for (var i = 0; i < 10; i++) {
    attachClick(i);
}

これ:

for (var i = 0; i < 10; i++) {
    (function (val) {
        $("#img" + val).click(
            function () { alert(val); }
        );
    })(i);
}

上記の単なるインライン宣言です。上記と同じ特性を持つ無名関数を宣言し、attachClickすぐに呼び出します。関数パラメーターを介してを渡す行為は、変数への参照を壊します。i

于 2012-11-22T05:43:48.450 に答える
2

decezeの答えに賛成しましたが、もっと簡単な説明を試してみようと思いました。クロージャが機能する理由は、javascriptの変数が関数スコープであるためです。クロージャーは新しいスコープを作成し、iinの値をパラメーターとして渡すことで、新しいスコープでローカル変数iを定義します。クロージャーがない場合、定義するすべてのクリックハンドラーは同じスコープ内にあり、同じを使用しiます。最後のコードスニペットが機能しない理由は、ローカルがないためです。したがって、すべてのクリックハンドラーは、定義されiた最も近い親コンテキストを探しています。i

私はあなたを混乱させるかもしれない他のことはこのコメントだと思います

オブジェクトは常に参照によって渡され、コピーされることはないため、自己実行関数呼び出しは違いを生まないはずです。

これはオブジェクトには当てはまりますが、プリミティブ値(数値など)には当てはまりません。これが、新しいローカルiを定義できる理由です。実例として、iの値を配列でラップするなどの奇妙なことをした場合、配列は参照によって渡されるため、クロージャーは機能しません。

// doesn't work
for(var i=[0]; i[0]<10; i[0]++) {
    (function (i2) {
        $("#img" + i2[0]).click(
            function () { alert(i2[0]); }
        );
    })(i);
}
于 2012-11-22T05:59:35.403 に答える
0

次の例を実行します。

for(var i=0; i<10; i++) {
     $("#img" + i).click(
          function () { alert(i); }
     );
}

i++;

あなたは今、11警告されているのを見るでしょう。iしたがって、関数パラメータとして値を送信することにより、への参照を回避する必要があります。あなたはすでに解決策を見つけました。

于 2012-11-22T05:47:09.377 に答える
0

iコード 1 とコード 3 は変数であり、各ループで値が変更されるため、機能しませんでした。ループの最後に10が割り当てられiます。

より明確にするために、この例を見てください。

for(var i=0; i<10; i++) {

}

alert(i)

http://jsfiddle.net/muthkum/t4Ur5/

ループの後に a を置くと、 valuealertのショーalertボックスが表示されることがわかります10

これがコード 1 とコード 3 に起こっていることです。

于 2012-11-22T05:43:56.943 に答える
0

最初の例では、 の値は 1 つだけで、それがループiで使用されています。forこれにより、すべてのイベント ハンドラーは、目的の値ではなく、ループが終了iしたときの値を表示します。for

2 番目の例ではi、イベント ハンドラーがインストールされた時点のの値がi2関数の引数にコピーされ、関数の呼び出しごと、つまりイベント ハンドラーごとに個別のコピーが存在します。

したがって、この:

(function (i2) {
    $("#img" + i2).click(
        function () { alert(i2); }
    );
 })(i);

i2関数の個別の呼び出しごとに独自の値を持つ新しい変数を作成します。JavaScript のクロージャーにより、の各個別のコピーがi2個別のイベント ハンドラーごとに保持されるため、問題が解決されます。

3 番目の例では、 の新しいコピーiは作成されない (それらはすべてループiから同じものを参照する) ため、最初の例と同じように機能します。for

于 2012-11-22T05:43:05.680 に答える
0

他の回答で言及されていないことの1つは、質問で示したこの例が機能しない理由です。

for(var i=0; i<5; i++) {
    var toggler = $("<img/>", { "src": "http://www.famfamfam.com/lab/icons/silk/icons/cross.png" });
    toggler.click(function () { toggler.attr("src", "http://www.famfamfam.com/lab/icons/silk/icons/tick.png"); });
    $("#container").append(toggler);
}

数か月後、JavaScript の理解を深めてこの質問に戻ると、JavaScript が機能しない理由は次のように理解できます。

  1. var toggler宣言は関数呼び出しの先頭に巻き上げられます。へのすべての参照togglerは、同じ実際の識別子へのものです。
  2. 無名関数で参照されるクロージャーはtoggler、ループの反復ごとに更新される を含むクロージャーと同じ (浅いコピーではない) です。

#2はかなり驚くべきことです。たとえば、これは「5」を警告します。

var o;
setTimeout(function () { o = {value: 5}; }, 100);
setTimeout(function () { alert(o.value) }, 1000);
于 2013-08-07T02:33:16.347 に答える