web-dev-qa-db-ja.com

ループでのJavaScriptクロージャーの使用について説明してください

私はクロージャーとループ内のクロージャーに関する多くの説明を読みました。概念を理解するのに苦労しています。私はこのコードを持っています:クロージャーの概念をより明確にするために、可能な限りコードを減らす方法はありますか。 iが2つの括弧内にある部分を理解するのに苦労しています。ありがとう

function addLinks () {
    for (var i=0, link; i<5; i++) {

        link = document.createElement("a");
        link.innerHTML = "Link " + i;


        link.onclick = function (num) {
            return function () {
                alert(num);
            };
        }(i);
        document.body.appendChild(link);

    }
}
window.onload = addLinks;
54
CMS scripting

警告:Long(ish)Answer

これは、社内のウィキで書いた記事から直接コピーされたものです:

質問:ループでクロージャーを適切に使用する方法は?クイックアンサー:関数ファクトリを使用します。

  for (var i=0; i<10; i++) {
    document.getElementById(i).onclick = (function(x){
      return function(){
        alert(x);
      }
    })(i);
  }

またはより読みやすいバージョン:

  function generateMyHandler (x) {
    return function(){
      alert(x);
    }
  }

  for (var i=0; i<10; i++) {
    document.getElementById(i).onclick = generateMyHandler(i);
  }

これは、JavaScriptや関数型プログラミングを初めて使用する人を混乱させることがよくあります。これは、クロージャとは何かを誤解した結果です。

クロージャは、単に変数の値や変数への参照を渡すだけではありません。クロージャは変数自体をキャプチャします!次のコードはこれを示しています。

  var message = 'Hello!';
  document.getElementById('foo').onclick = function(){alert(message)};
  message = 'Goodbye!';

要素「foo」をクリックすると、「Goodbye!」というメッセージを含む警告ボックスが生成されます。このため、ループで単純なクロージャーを使用すると、すべてのクロージャーが同じ変数を共有することになり、その変数にはループで割り当てられた最後の値が含まれます。例えば:

  for (var i=0; i<10; i++) {
    document.getElementById('something'+i).onclick = function(){alert(i)};
  }

すべての要素をクリックすると、番号10のアラートボックスが生成されます。実際、i="hello";すべての要素が「hello」アラートを生成します!変数iは、10個の関数と現在の関数/スコープ/コンテキストで共有されます。これは、関係する関数のみが見ることができる一種のプライベートグローバル変数と考えてください。

必要なのは、その変数のインスタンス、または少なくとも変数自体ではなく変数への単純な参照です。幸いなことに、javascriptには、参照(オブジェクトの場合)または値(文字列および数値の場合)を渡すメカニズムが既にあります。関数の引数です。

JavaScriptで関数が呼び出されると、その関数の引数は、オブジェクトの場合は参照によって、文字列または数値の場合は値によって渡されます。これはクロージャーで変数の共有を壊すのに十分です。

そう:

  for (var i=0; i<10; i++) {
    document.getElementById(i).onclick =
      (function(x){ /* we use this function expression simply as a factory
                       to return the function we really want to use: */

        /* we want to return a function reference
           so we write a function expression*/
        return function(){
          alert(x); /* x here refers to the argument of the factory function
                       captured by the 'inner' closure */
        }

      /* The brace operators (..) evaluates an expression, in this case this
         function expression which yields a function reference. */

      })(i) /* The function reference generated is then immediately called()
               where the variable i is passed */
  }
110
slebetman

私は長い間JavaScriptでプログラミングを行ってきましたが、「ループ内のクロージャ」は非常に広範なトピックです。その内部関数が後で実行されるときにループの「現在の値」を保持するために、forループ内で(function(param) { return function(){ ... }; })(param);を使用する方法について話していると思います...

コード:

for(var i=0; i<4; i++) {
  setTimeout(
    // argument #1 to setTimeout is a function.
    // this "outer function" is immediately executed, with `i` as its parameter
    (function(x) {
      // the "outer function" returns an "inner function" which now has x=i at the
      // time the "outer function" was called
      return function() {  
        console.log("i=="+i+", x=="+x);
      };
    })(i) // execute the "closure" immediately, x=i, returns a "callback" function
  // finishing up arguments to setTimeout
  , i*100);
}

出力:

i==4, x==0
i==4, x==1
i==4, x==2
i==4, x==3

出力からわかるように、すべての内部コールバック関数はすべて同じiを指していますが、それぞれに独自の「クロージャー」があるため、xの値は実際には次のように格納されます。外部関数の実行時にiが何であれ。

通常、このパターンを見るときは、パラメーターと同じ変数名と外部関数への引数を使用します。たとえば、(function(i){ })(i)です。その関数内のコードは(コールバック関数のように後で実行される場合でも)、「外部関数」を呼び出したときにiを参照します。

10
gnarf

そのような場合のクロージャーの「問題」は、iへのアクセスが同じ変数を参照することです。これは、_ECMA-/Javascripts_ _function scope_または_lexical scope_が原因です。

したがって、alert(i);へのすべての呼び出しが_5_を表示することを回避するには(ループが終了した後i === 5のため)、実行時にそれ自体を呼び出す新しい関数を作成する必要があります。

これを実現するには、新しい関数を作成する必要があり、さらに_invoke the outer function_への最後に余分な括弧が必要なので、_link.onclick_に戻り関数が参照として含まれるようになります。

4
jAndy

クロージャは、定義されているスコープの外側で変数を参照する構造です。通常、関数のコンテキストでクロージャについて話します。

_var helloFunction;
var finished = false;

while (!finished) {
 var message = 'Hello, World!';
 helloFunction = function() {
   alert(message);
 }
 finished = true;
}

helloFunction();
_

ここでは、変数messageを定義し、messageを参照する関数を定義します。使用する関数を定義するときにmessage、を作成していますclosure。これはhelloFunctionmessageへの参照を保持することを意味します=、messageが定義されているスコープ(ループ本体)の外側であっても、引き続きmessageを使用できるようにします。

補遺

(i)括弧内は関数呼び出しです。何が起こっているのですか:

  1. いくつかのfunction(num){}を定義します。これは匿名関数と呼ばれます。インラインで定義されており、名前がないためです。
  2. function(num)は整数引数を取り、alert(num)として定義されている別の関数への参照を返します
  3. 引数iを使用して、外部の匿名関数がすぐに呼び出されます。 num== i。この呼び出しの結果は、alert(i)を実行する関数です。
  4. 最終結果は多かれ少なかれ以下と同等です:link.onclick = function() { alert(i); };
2
RMorrisey

質問の最後の部分に答えるため。 2つの括弧は、他の関数と同様に関数を呼び出します。ここでそれを行う理由は、その時点で変数「i」が何であるかを維持したいからです。そのため、関数を呼び出して、引数「num」としてiを送信します。呼び出されるので、変数リンク自身のスクープの値numeを記憶します。

このすべてのリンクをクリックしなかった場合、「5」というアラートが表示されます

JQueryの創設者であるJohn Resigは、これを説明する本当に素晴らしいオンラインプレゼンテーションを持っています。 http://ejohn.org/apps/learn/

..fredrik

0
fredrik