正しい「this」コンテキストをsetTimeoutコールバックに渡しますか?


249

コンテキストをどのように渡すのsetTimeoutですか?1000ms後this.tip.destroy()なら電話したいthis.options.destroyOnHideです。どうやってやるの?

if (this.options.destroyOnHide) {
     setTimeout(function() { this.tip.destroy() }, 1000);
} 

上記を試すとthis、ウィンドウを指します。


4
重複フラグは本当に有効ですか?この質問は実際には以前に尋ねられました。
Sui Dream

1
if(this.options.destroyOnHide){setTimeout(function(){this.tip.destroy()} .bind(this)、1000); }
Zibri

回答:


352

編集:要約すると、この問題がこの問題を解決する最も一般的な方法が尋ねられた2010年に戻って、グローバルオブジェクトをポイントして関数を実行するsetTimeoutため、関数呼び出しが行われるコンテキストへの参照を保存することでした:setTimeoutthis

var that = this;
if (this.options.destroyOnHide) {
     setTimeout(function(){ that.tip.destroy() }, 1000);
} 

その1年前にリリースされたばかりのES5仕様では、bindメソッドが導入されました。これはまだ広くサポートされておらず、使用するためにポリフィルが必要だったため、元の回答では提案されていませんでしたが、今ではすべての場所にあります。

if (this.options.destroyOnHide) {
     setTimeout(function(){ this.tip.destroy() }.bind(this), 1000);
}

bind関数が持つ新しい機能を作成this価値予め充填されています。

現在のJSでは、これはまさにES6で矢印関数が解決する問題です:

if (this.options.destroyOnHide) {
     setTimeout(() => { this.tip.destroy() }, 1000);
}

矢印関数にはthis独自の値はありません。アクセスすると、それthisを囲む字句スコープの値にアクセスします。

また、HTML5はタイマーを2011年に標準化し、コールバック関数に引数を渡すことができるようになりました。

if (this.options.destroyOnHide) {
     setTimeout(function(that){ that.tip.destroy() }, 1000, this);
}

以下も参照してください。


3
できます。私はjsbinスクリプトでコンセプトをテストしました:jsbin.com/etise/7/edit
John K

1
このコードでは、不要な変数(関数全体のスコープを持つ変数)を作成します。this関数に正しく渡された場合、この場合、map()、forEach()などで、この問題は、より少ないコード、より少ないCPUサイクル、およびより少ないメモリを使用して解決したことになります。***参照:ミシャ・レイズリンの答え。
HoldOffHunger 2017年

222

回答済みの関数ラッパー@CMSへの既成のショートカット(構文糖)があります。(以下では、必要なコンテキストがであると想定していますthis.tip。)


ECMAScript 5現在のブラウザー、Node.js)およびPrototype.js

ECMA-262、第5版(ECMAScript 5)またはNode.js互換性のあるブラウザーをターゲットにする場合は、を使用できますFunction.prototype.bind。オプションで関数の引数を渡して、部分的な関数を作成できます

fun.bind(thisArg[, arg1[, arg2[, ...]]])

もう一度、あなたのケースでは、これを試してください:

if (this.options.destroyOnHide) {
    setTimeout(this.tip.destroy.bind(this.tip), 1000);
}

同じ機能がプロトタイプ(他のライブラリ?)にも実装されています。

Function.prototype.bindカスタムの下位互換性が必要場合は、このように実装できます(ただし、注意事項に従ってください)。


ECMAScript 2015一部のブラウザー、Node.js 5.0.0以降)

最先端の開発(2015)では、ECMAScript 2015(Harmony / ES6 / ES2015)仕様の一部であるファットアロー関数を使用できます()。

矢印関数式(としても知られている脂肪の矢印機能は)関数式に比べて短い構文を持ち、字句バインドするthis値を[...]。

(param1, param2, ...rest) => { statements }

あなたの場合、これを試してください:

if (this.options.destroyOnHide) {
    setTimeout(() => { this.tip.destroy(); }, 1000);
}

jQuery

jQuery 1.4+をすでに使用しているthis場合は、関数のコンテキストを明示的に設定するための既製の関数があります。

jQuery.proxy():関数を取り、常に特定のコンテキストを持つ新しい関数を返します。

$.proxy(function, context[, additionalArguments])

あなたの場合、これを試してください:

if (this.options.destroyOnHide) {
    setTimeout($.proxy(this.tip.destroy, this.tip), 1000);
}

Underscore.jslodash

それはUnderscore.jsで利用できるだけでなく、lodash、として_.bind(...)12

bind関数をオブジェクトにバインドします。つまり、関数が呼び出されるたびに、の値がthisオブジェクトになります。必要に応じて、引数を関数にバインドして事前に入力します。部分適用とも呼ばれます。

_.bind(function, object, [*arguments])

あなたの場合、これを試してください:

if (this.options.destroyOnHide) {
    setTimeout(_.bind(this.tip.destroy, this.tip), 1000);
}


なぜデフォルトではないのですfunc.bind(context...)か?私は何かを逃していますか?
aTei

これを呼び出すたびに、(bindが行う)新しい関数を常に作成し続けることはパフォーマンスに優れていますか?キーを押すたびにリセットする検索タイムアウトがあり、再利用のためにこの「バインドされた」メソッドをどこかにキャッシュする必要があるようです。
Triynko、2015

@Triynko:関数をバインドすることは負荷の高い操作とは思えませんが、同じバインドされた関数を複数回呼び出す場合はvar boundFn = fn.bind(this); boundFn(); boundFn();、たとえば参照を保持することもできます。
Joel Purra

30

Internet Explorer以外のブラウザーでは、遅延後にパラメーターを関数に一緒に渡すことができます。

var timeoutID = window.setTimeout(func, delay, [param1, param2, ...]);

だから、これを行うことができます:

var timeoutID = window.setTimeout(function (self) {
  console.log(self); 
}, 500, this);

これは、スコープのルックアップ(thisタイムアウト/間隔式の外の変数へのキャッシュ)、および($.proxyまたはを使用したFunction.prototype.bind)クロージャーの作成よりもパフォーマンスの点で優れています。

WebreflectionからIEで機能させるコード:

/*@cc_on
(function (modifierFn) {
  // you have to invoke it as `window`'s property so, `window.setTimeout`
  window.setTimeout = modifierFn(window.setTimeout);
  window.setInterval = modifierFn(window.setInterval);
})(function (originalTimerFn) {
    return function (callback, timeout){
      var args = [].slice.call(arguments, 2);
      return originalTimerFn(function () { 
        callback.apply(this, args) 
      }, timeout);
    }
});
@*/

1
プロトタイプチェーンを使用してクラスを作成し、メソッドがプロトタイプメソッドである場合、「バインド」は、メソッド内の「これ」を変更する唯一のものです。コールバックにパラメーターを渡すことで、関数内の「this」が何であるかを変更しないため、他のプロトタイプメソッドのように、その中で「this」を使用してそのようなプロトタイプ関数を作成できませんでした。それは矛盾につながります。Bindは実際に必要なものに最も近いものであり、クロージャを「this」にキャッシュして、ルックアップのパフォーマンスを高め、2回以上作成する必要がないようにすることができます。
Triynko、2015

3

注:これはIEでは機能しません

var ob = {
    p: "ob.p"
}

var p = "window.p";

setTimeout(function(){
    console.log(this.p); // will print "window.p"
},1000); 

setTimeout(function(){
    console.log(this.p); // will print "ob.p"
}.bind(ob),1000);

2

を使用している場合はunderscore、を使用できますbind

例えば

if (this.options.destroyOnHide) {
     setTimeout(_.bind(this.tip.destroy, this), 1000);
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.