Pass correct "this" context to setTimeout callback?
How do I pass context into setTimeout
? I want to call this.tip.destroy()
if this.options.destroyOnHide
after 1000 ms. How can I do that?
if (this.options.destroyOnHide) {
setTimeout(function() { this.tip.destroy() }, 1000);
}
When I try the above, this
refers to the window.
EDIT: In summary, back in 2010 when this question was asked the most common way to solve this problem was to save a reference to the context where the setTimeout
function call is made, because setTimeout
executes the function with this
pointing to the global object:
var that = this;
if (this.options.destroyOnHide) {
setTimeout(function(){ that.tip.destroy() }, 1000);
}
In the ES5 spec, just released a year before that time, it introduced the bind
method, this wasn't suggested in the original answer because it wasn't yet widely supported and you needed polyfills to use it but now it's everywhere:
if (this.options.destroyOnHide) {
setTimeout(function(){ this.tip.destroy() }.bind(this), 1000);
}
The bind
function creates a new function with the this
value pre-filled.
Now in modern JS, this is exactly the problem arrow functions solve in ES6:
if (this.options.destroyOnHide) {
setTimeout(() => { this.tip.destroy() }, 1000);
}
Arrow functions do not have a this
value of its own, when you access it, you are accessing the this
value of the enclosing lexical scope.
HTML5 also standardized timers back in 2011, and you can pass now arguments to the callback function:
if (this.options.destroyOnHide) {
setTimeout(function(that){ that.tip.destroy() }, 1000, this);
}
See also:
- setTimeout - The 'this' problem
There are ready-made shortcuts (syntactic sugar) to the function wrapper @CMS answered with. (Below assuming that the context you want is this.tip
.)
ECMAScript 2015 (all common browsers and smartphones, Node.js 5.0.0+)
For virtually all javascript development (in 2020) you can use fat arrow functions, which are part of the ECMAScript 2015 (Harmony/ES6/ES2015) specification.
An arrow function expression (also known as fat arrow function) has a shorter syntax compared to function expressions and lexically binds the
this
value [...].
(param1, param2, ...rest) => { statements }
In your case, try this:
if (this.options.destroyOnHide) {
setTimeout(() => { this.tip.destroy(); }, 1000);
}
ECMAScript 5 (older browsers and smartphones, Node.js) and Prototype.js
If you target browser compatible with ECMA-262, 5th edition (ECMAScript 5) or Node.js, which (in 2020) means all common browsers as well as older browsers, you could use Function.prototype.bind
. You can optionally pass any function arguments to create partial functions.
fun.bind(thisArg[, arg1[, arg2[, ...]]])
Again, in your case, try this:
if (this.options.destroyOnHide) {
setTimeout(this.tip.destroy.bind(this.tip), 1000);
}
The same functionality has also been implemented in Prototype (any other libraries?).
Function.prototype.bind
can be implemented like this if you want custom backwards compatibility (but please observe the notes).
jQuery
If you are already using jQuery 1.4+, there's a ready-made function for explicitly setting the this
context of a function.
jQuery.proxy(): Takes a function and returns a new one that will always have a particular context.
$.proxy(function, context[, additionalArguments])
In your case, try this:
if (this.options.destroyOnHide) {
setTimeout($.proxy(this.tip.destroy, this.tip), 1000);
}
Underscore.js, lodash
It's available in Underscore.js, as well as lodash, as _.bind(...)
1,2
bind Bind a function to an object, meaning that whenever the function is called, the value of
this
will be the object. Optionally, bind arguments to the function to pre-fill them, also known as partial application.
_.bind(function, object, [*arguments])
In your case, try this:
if (this.options.destroyOnHide) {
setTimeout(_.bind(this.tip.destroy, this.tip), 1000);
}
bind jquery underscore.js ecmascript-5 prototypejs node.js
In browsers other than Internet Explorer, you can pass parameters to the function together after the delay:
var timeoutID = window.setTimeout(func, delay, [param1, param2, ...]);
So, you can do this:
var timeoutID = window.setTimeout(function (self) {
console.log(self);
}, 500, this);
This is better in terms of performance than a scope lookup (caching this
into a variable outside of the timeout / interval expression), and then creating a closure (by using $.proxy
or Function.prototype.bind
).
The code to make it work in IEs from 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);
}
});
@*/
NOTE: This won't work in 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);