1

重複の可能性:
JavaScript クロージャーはどのように機能しますか?

FAQ を検索し、例を見てきましたが、これが機能しない理由を理解できないようです。私が間違っていることについてのヒントをいただければ幸いです。私がやろうとしているのは、単語を取り、ボタンをクリックすると、文字ごとに一度に1つずつ画像を表示し、単語を綴ることです(画像はフェードイン/フェードアウトする必要があります)。これは古典的な「for ループが最後の項目のみを表示する」問題ですが、問題は、コンソールが正しくログを記録することです。変数は変更されますが、最後の画像のみが表示されます。繰り返しますが、これを完全に理解することが重要であることを知っているので、私が間違っていることを理解する助けに本当に感謝しています. 以下のコード (更新されるのは div とボタンだけなので、HTML は省略しました):

$(document).ready(function () {
  var word = 'abc';

  $('#newWordButton').click(function () {
    function animateLetters() {
      function changeLetter() {
        for (i = 0; i < word.length; i++) {
          var currentLetter = word.charAt(i);
          console.log(currentLetter);
          $('#wordsDiv').fadeOut(1000, function () {
            $('#wordsDiv').replaceWith('<img src = "images/letters/' + currentLetter + '.gif" />');
            $('#wordsDiv').fadeIn(1000);
          });
        }
      }
      setTimeout(changeLetter, 1000);
    }

    animateLetters();
  });
});
4

4 に答える 4

0

JavaScriptは関数スコープです。新しい関数を作成するたびに、次のようになります。

  1. 独自に宣言された変数(引数変数を含む)を保持するスコープオブジェクトを取得します
  2. 関数自体のスコープに存在しない変数にアクセスしようとすると、実行時にウォークされる、それが定義されたスコープへの参照があります。

したがって、コードのわずかに編集されたバージョンを見ると(varループに追加されているためiグローバルではないため、animateLetters()ラッパーレイヤーが削除されています):

$(document).ready(function () {
  var word = 'abc';

  $('#newWordButton').click(function () {
    function changeLetter() {
      for (var i = 0; i < word.length; i++) {
        var currentLetter = word.charAt(i);
        $('#wordsDiv').fadeOut(1000, function () {
          $('#wordsDiv').replaceWith('<img src = "images/letters/' + currentLetter + '.gif" />');
          $('#wordsDiv').fadeIn(1000);
        });
      }
    }
    setTimeout(changeLetter, 1000);
  });
});

生成される結果のスコープは次のようになります(スコープは角かっこで表されます)。

[global] - this is the fallback scope, and how every reference to $ is resolved
| var $
+-[function()] (passed to ready call)
  | var word
  +-[function()] (passed to click call)
    | var changeLetter
    +-[function changeLetter()]
      | var i = 4
      | var currentLetter = 'c'
      +-[function()] (passed to 1st fadeOut call)
      | |
      +-[function()] (passed to 2nd fadeOut call)
      | |
      +-[function()] (passed to 3rd fadeOut call)
        |

フェードアウト関数が呼び出されると、スコープチェーンをたどって、アクセス時に変数を見つけますcurrentLetter。これは、独自のスコープに変数がないためです。それらはすべて同じ親スコープにアクセスできるため、ループが終了currentLetterした後、を指す同じ変数にアクセスしていることに注意してください。for'c'


ループが終了した後、 @ gpojdのソリューションを使用すると(ただし、ループ式に追加することを忘れないでくださいvar i!)、スコープ図の下部は次のようになります。for

[function changeLetter()]
| var i = 4
+-[function()] (IIFE in 1st loop iteration)
| | var currentLetter = 'a'
| +-[function()] (passed to 1st fadeOut call)
|   |
+-[function()] (IIFE in 2nd loop iteration)
| | var currentLetter = 'b'
| +-[function()] (passed to 2nd fadeOut call)
|   |
+-[function()] (IIFE in 3rd loop iteration)
  | var currentLetter = 'c'
  +-[function()] (passed to 3rd fadeOut call)
    |

解決策は、即時呼び出し関数式(IIFE)を使用して、ループの反復ごとに中間関数スコープを作成します。これにより、現在の値の文字の値が、独自のスコープで変数にiなるものに渡されます。関数が呼び出され、IIFEによって作成された関数スコープ内currentLetterの変数を見つけて使用します。currentLetter

于 2012-11-19T17:31:02.713 に答える
0

簡単に言うと、クロージャは、山括弧のペアだけでなく、関数のスコープによって定義されます。forループは関数定義ではないため、クロージャを形成しません。

@gpojdは、実行可能なコードを提供したと思います。ただし、閉鎖に関するFAQには、次のような詳細情報があります。

于 2012-11-19T17:00:43.257 に答える
0

閉鎖はあなたの問題の半分ではありません...

for ループにより、実際には何もアニメーション化されませんでした... を画像に置き換えて#wordsDivいたため、次の画像に存在する可能性はありませんでした。

ここを参照してください: http://jsfiddle.net/P8Lz5/

$(document).ready(function() {
    var word = 'pneumonoultramicroscopicsilicovolcanoconiosis';
    $('#newWordButton').click(animateLetters);

    function animateLetters() {
        (function changeLetter(i) {
            if (i == word.length) return;

            var currentLetter = word.charAt(i);
            console.log(currentLetter);

            $('#wordsDiv').empty();
            var img = new Image();
            img.src = 'http://placehold.it/100x100/&text=' + currentLetter;

            img.onload = function() {
                $(img).appendTo('#wordsDiv').hide().fadeIn(1000, function() {
                    $('#wordsDiv img').delay(1000).fadeOut(1000, function() {
                        changeLetter(i + 1);
                    });
                });
            };
        })(0);
    }
});​

または、文字を 1 つずつフェードインして単語を形成する場合は、こちらを参照してください: http://jsfiddle.net/P8Lz5/1/

于 2012-11-19T17:18:43.187 に答える
0

currentLetter値を引数として自己実行関数に渡して、クロージャを作成する必要があります。これにより、関数の実行時に値が保持されます。

テストされていない例:

for (i = 0; i < word.length; i++) {
    (function (currentLetter) {
        console.log(currentLetter);
        $('#wordsDiv').fadeOut(1000, function () {
            $('#wordsDiv').replaceWith('<img src = "images/letters/' + currentLetter + '.gif" />');
            $('#wordsDiv').fadeIn(1000);
        });
    )(word.charAt(i));
}
于 2012-11-19T16:56:45.590 に答える