将正确的“ this”上下文传递给setTimeout回调?


249

如何传递上下文setTimeout?我想打电话this.tip.destroy(),如果this.options.destroyOnHide在1000毫秒。我怎样才能做到这一点?

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 '18

Answers:


352

编辑:总而言之,早在2010年,当有人问这个问题时,解决此问题的最常用方法是保存对进行setTimeout函数调用的上下文的引用,因为setTimeout执行函数时要this指向全局对象:

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

在一年前发布的ES5规范中,它介绍了该bind方法,但原始答案中并未建议使用该方法,因为该方法尚未得到广泛支持,您需要使用polyfills来使用它,但是现在无处不在:

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 2010年

1
此代码涉及制作不必要的变量(具有函数范围的范围);如果您正确地传递this给函数,则可以使用更少的代码,更少的CPU周期和更少的内存来解决此问题,例如map(),forEach()等。***请参阅:Misha Reyzlin的答案。
HoldOffHunger

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

@Triynko:我不认为绑定函数是一项昂贵的操作,但是如果多次调用同一个绑定函数,则最好保留一个引用:var boundFn = fn.bind(this); boundFn(); boundFn();例如。
乔尔·普拉

30

在Internet Explorer以外的浏览器中,可以在延迟后将参数一起传递给函数:

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

因此,您可以这样做:

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

就性能而言,这比范围查找(将其缓存this到timeout / interval表达式之外的变量中),然后创建闭包(使用$.proxyFunction.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”,因此无法像其他任何原型方法一样在其中使用“ this”来编写此类原型函数。这导致不一致。绑定是最接近我们实际需要的东西,闭包可以缓存在“ this”中以提高查找性能,而不必多次创建。
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);
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.