web-dev-qa-db-ja.com

クロージャから可変変数へのアクセスを回避する方法

私はこのようないくつかのコードを持っています:

for(var id=0; id < message.receiver.length; id++){
   var tmp_id = id;
   zlib.gzip(JSON.stringify(message.json), function(err, buffer){
                        ...
   pushStatusPool[message.receiver[tmp_id]] = null; // fix memory leak
   delete pushStatusPool[message.receiver[tmp_id]];
   ...
   });
}

また、クロージャでtmp_idを使用すると、可変変数であるため問題が発生する可能性があるという警告が表示されました。

どうすればそれを回避できますか?これはforループであり、zlib.gzipのコードを変更できないため、不変の変数をコールバックに送信するにはどうすればよいですか?言い換えれば、どうすればクロージャに引数を渡すことができますか?

32
bxshi

自己実行関数を使用してtmp_idを正しくキャプチャするには、スコープを作成する必要があります。これは、forループ全体が1つのスコープであるためです。つまり、通過するたびに、同じ変数をキャプチャしていることになります。したがって、コールバックが呼び出される前にtemp_idの値が変更されるため、コールバックは間違ったIDで終了します。

ただし、警告は無視(またはシャットオフ)しますが、temp_idは変更可能であるため、再割り当てする可能性があると不平を言っているようです。それはちょっとばかげています。本当に修正したい場合は、constの代わりにvarキーワードを使用してみてください。

for(var id=0; id < message.receiver.length; id++){
   (function(){
       const tmp_id = id;
       zlib.gzip(JSON.stringify(message.json), function(err, buffer){
                        ...
           pushStatusPool[message.receiver[tmp_id]] = null; // fix memory leak
           delete pushStatusPool[message.receiver[tmp_id]];
           ...
       });
   })();
}
41
user24359

私は同じ問題に直面し、IDをクロージャーに渡すことで、user24359の回答をわずかに変更して解決しました。

for(var id=0; id < message.receiver.length; id++){
   (function(tmp_id){
       zlib.gzip(JSON.stringify(message.json), function(err, buffer){
                        ...
           pushStatusPool[message.receiver[tmp_id]] = null; // fix memory leak
           delete pushStatusPool[message.receiver[tmp_id]];
           ...
       });
   })(id);
}
10
hennadiy.verkh

ここでは、user24359のすばらしい答えを簡略化しています。これが解決策です:

var object = {a:1,b:2};

for (var y in object){
    (function(){const yyy = y;
        setTimeout(function(){console.log(yyy)},3000);})();
}

上記のコードはabをログに記録し、解決策です。次のコードログbb:

var object = {a:1,b:2};
for (var y in object){

    setTimeout(function(){console.log(y)},3000);
}
2
Michael Moeller

@ user24359の回答は良い解決策ですが、varキーワードをletキーワードに置き換えるだけです。

for(var id=0;

になります

for(let id=0;

詳細を見る ここ

編集:HeribertoJuárezが提案したように、EcmaScript6をサポートするブラウザーでのみ機能します。

0
Bludwarf

分度器でも同じ問題に直面しました。次のコードを使用して解決しました-

(function(no_of_agents){
              ptor.element.all(by.repeater('agent in agents').column('displayName')).then(function(firstColumn){
                    console.log(i, '>>>>>Verifying the agent Name');
                    var agentsSorted = sortAgentsByName();
                    //verify the agent name
                    expect(firstColumn[no_of_agents].getText()).toEqual(agentsSorted[no_of_agents].name);
                    //now click on the agent name link
                    firstColumn[no_of_agents].click();
                    ptor.sleep(5000);
              });
            })(no_of_agents);
0
Piyush Jajoo

vartmp_id)がコールバック関数の上位スコープにあるループでクロージャを作成することは、 よくある間違いvarがブロックスコープされていないため、これは避ける必要があります。このため、またループ内で作成された各クロージャは同じ 字句環境 を共有するため、変数は常に最後の反復値になります(つまり、message.receiver.length - 1 as tmp_id)コールバック関数が呼び出されたとき。あなたのIDEはこの振る舞いを検出し、正しく文句を言います。

警告を回避するには、いくつかの解決策があります。

  • varletに置き換えて、作成された各クロージャに独自のスコープtmp_idが定義されるようにします。反復:

    for (var id = 0; id < message.receiver.length; id++) {
      let tmp_id = id;
      zlib.gzip(JSON.stringify(message.json), function(err, buffer) {
        // Do something with tmp_id ...
      });
    }
    
  • gennadi.w did のように [〜#〜] iife [〜#〜] を活用して、各反復で字句環境を作成します。

  • ファクトリ関数(createCallback)を使用して、各反復でコールバック関数を作成します。

    const createCallback = tmp_id => function(err, buffer) {
      // Do something with tmp_id ...
    };
    for (var id = 0; id < message.receiver.length; id++) {
      zlib.gzip(JSON.stringify(message.json), createCallback(id));
    }
    
  • bind それらが取得するコールバック関数の変数追加そのパラメーター:

    for (var id = 0; id < message.receiver.length; id++) {
      zlib.gzip(JSON.stringify(message.json), function(tmp_id, err, buffer) {
        // Do something with tmp_id (passed as id) ...
      }.bind(this, id));
    }
    

可能であれば、ECMAScript 2015の時点では、このようなエラーが発生しやすい動作のため、varは避ける必要があります。

0