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

317

如何将上下文传递给setTimeout?我想在1000毫秒后调用this.options.destroyOnHide以销毁this.tip.destroy()。我该怎么做?

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

当我尝试上述方法时,this 指的是窗口对象。


4
重复标记是否真的有效?实际上早些时候就有人问过这个问题。 - Sui Dream
1
如果(this.options.destroyOnHide){ setTimeout(function() { this.tip.destroy() }.bind(this), 1000); } - Zibri
6个回答

478

编辑: 总的来说,回到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);
}

Arrow函数没有自己的this值,当您访问它时,您正在访问封闭词法范围的this值。HTML5也在2011年标准化定时器,现在您可以向回调函数传递参数:
if (this.options.destroyOnHide) {
     setTimeout(function(that){ that.tip.destroy() }, 1000, this);
}

另请参阅:


4
可以。我用一个 jsbin 脚本测试了这个概念,它有效:http://jsbin.com/etise/7/edit - John K
1
这段代码涉及创建一个不必要的变量(该变量具有函数范围);如果您正确地将“this”传递给函数,则可以为此情况、map()、forEach()等解决此问题,使用更少的代码、更少的CPU周期和更少的内存。***请参见:Misha Reyzlin的答案。 - HoldOffHunger

240

有现成的快捷方式(语法糖)可以使用函数包装器 @CMS 的回答。 (以下假设您想要的上下文是 this.tip。)


ECMAScript 2015所有常见的浏览器和智能手机, Node.js 5.0.0+)

对于几乎所有的JavaScript开发(在2020年),您可以使用fat arrow functions,它们是ECMAScript 2015(Harmony/ES6/ES2015)规范的一部分

箭头函数表达式(也称为fat arrow function)与函数表达式相比具有更短的语法,并且在词法上绑定了this值[...]。

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

在您的情况下,请尝试这个:
if (this.options.destroyOnHide) {
    setTimeout(() => { this.tip.destroy(); }, 1000);
}

ECMAScript 5旧版浏览器和智能手机,{Node.js})和Prototype.js

如果你的目标是与ECMA-262第5版(ECMAScript 5)兼容的浏览器Node.js,这意味着(截至2020年)所有常见浏览器以及旧版浏览器都可以使用{{link5: Function.prototype.bind }}。 你可以选择性地传递任何函数参数来创建偏函数

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

再次针对您的情况,尝试这样做:
if (this.options.destroyOnHide) {
    setTimeout(this.tip.destroy.bind(this.tip), 1000);
}

相同的功能也已经在Prototype中实现了(还有其他库吗?)。

如果您想要自定义向后兼容性,可以像这样实现Function.prototype.bind(但请注意说明)。


jQuery

如果您已经使用 jQuery 1.4+,那么有一个现成的函数可以显式设置函数的this上下文。

jQuery.proxy():接受一个函数并返回一个新函数,该函数将始终具有特定的上下文。

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

在你的情况下,试试这个:
if (this.options.destroyOnHide) {
    setTimeout($.proxy(this.tip.destroy, this.tip), 1000);
}

Underscore.js, lodash

它在Underscore.js和lodash中都可以使用,作为_.bind(...)1,2

bind将一个函数绑定到一个对象上,这意味着每当该函数被调用时,this的值将是该对象。还可以选择将参数绑定到函数上以预先填充它们,也称为部分应用。

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

在你的情况下,请尝试这样做:
if (this.options.destroyOnHide) {
    setTimeout(_.bind(this.tip.destroy, this.tip), 1000);
}


为什么不能默认使用 func.bind(context...)?我有遗漏什么吗? - aTei
每次调用时不断创建新函数(绑定操作)是否具有性能?我有一个搜索超时,在每次按键后重置,似乎应该将此“绑定”方法缓存以供重复使用。 - Triynko
@Triynko:我不认为绑定一个函数是一项昂贵的操作,但如果你多次调用同一个绑定的函数,最好保留一个引用:var boundFn = fn.bind(this); boundFn(); boundFn(); 例如。 - Joel Purra

32

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

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

所以,你可以这样做:

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

与作用域查找(将this缓存到超时/间隔表达式外部的变量中),然后创建一个闭包(使用$.proxyFunction.prototype.bind)相比,这种方法在性能方面更好。

使其在IE中运行的代码来自Webreflection

/*@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
使用原型链创建类并且你的方法是原型方法时,'bind' 是唯一能够改变方法内 'this' 的东西。通过向回调函数传递参数,你不会改变函数内 'this' 的值,因此这样的原型函数不能像其他原型方法一样在其中使用 'this'。这导致了不一致性。Bind 是我们实际想要的最接近的东西,闭包可以被缓存在 'this' 中以提高查找性能,而且不必再次创建它。 - Triynko

5

注意:这在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);
}

0
如果您正在使用TypeScript,可以将函数作为参数传递,如下所示:
setTimeout(this.tip.destroy, 1000);

而且,this 上下文将被分配,就好像您在 JavaScript 中封装了箭头函数的调用一样。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接