JavaScript setTimeout, using call to change this - javascript

I have two simple links that when clicked should wait one second and then add a class that changes the color of the text. The working version uses $.proxy and the non-working version I'm trying to use native JavaScript to change the meaning of this. Why is btnWaitNoProxy this still referring to the global object?
fiddle
code:
var obj = {
wait: function () {
setTimeout(function () {
console.log('inside the setTimeout');
$(this).addClass('lesson');
//refers to global object in the console
}, 1000);
}
};
$('#btnProxy').on('click', function () {
console.log('preparing to add class...');
setTimeout($.proxy(function () {
$(this).addClass('lesson')
console.log(this);
}, this), 1000);
});
$('#btnWaitNoProxy').on('click', function () {
console.log(this);
//call still refers to the global object
obj.wait.call(this);
});

Because you are using setTimeout in wait, so that callback method passed to setTimeout will be executed in the global context by default.
One possible solution is to use $.proxy() as you have done again with the setTimeout handler
var obj = {
wait: function () {
setTimeout($.proxy(function () {
console.log('inside the setTimeout');
$(this).addClass('lesson');
//refers to global object in the console
}, this), 1000);
}
};
Another is to use a closure variable like
var obj = {
wait: function () {
var self = this;
setTimeout(function () {
console.log('inside the setTimeout');
$(self).addClass('lesson');
//refers to global object in the console
}, 1000);
}
};

When you pass a method to setTimeout() (or any other function, for that matter), it will be invoked with a wrong this value.
Code executed by setTimeout() is run in a separate execution context to the function from which it was called. As a consequence, the this keyword for the called function will be set to the window (or global) object; it will not be the same as the this value for the function that called setTimeout.
Source: MDN — window.setTimeout
There are plenty solutions:
Using $.proxy
Using Function.prototype.bind
Aliasing this

Related

What is "this" when in the callback of setTimeout? [duplicate]

This question already has answers here:
Pass correct "this" context to setTimeout callback?
(6 answers)
Closed 5 years ago.
Apologies for the newbie question, but consider this code
function Greeter( name) { this.name = name; }
Greeter.prototype.delayed_greet = function() {
setTimeout( function cb() {
console.log(' Hello ' + this.name);
}, 500);
};
Greeter.prototype.greet = function() {
console.log(' Hello ' + this.name);
}
const greeter = new Greeter(' World');
greeter.delayed_greet(); // will print "Hello undefined"
greeter.greet(); // will print "Hello World"
So in the delayed_greet method, what does this refer to when it's nested inside the setTimeout? It's obviously not referring to the greeter object otherwise it would work.
setTimeout is generally defined as window.setTimeout in browsers, and can be called as just setTimeout because it's available in the global scope.
That also means the context, and this value, is always window, unless another this value is explicitly set.
MDN says
Code executed by setTimeout() is called from an execution context
separate from the function from which setTimeout was called.
The usual rules for setting the this keyword for the called function
apply, and if you have not set this in the call or with bind, it
will default to the global (or window) object in non–strict mode, or
be undefined in strict mode.
It will not be the same as the this value for the function that
called setTimeout.
MDN also outlines a number of ways to solve the "this-problem" in setTimeout.
Personally I think I would just take the easy way out, and use a variable
Greeter.prototype.delayed_greet = function() {
var that = this;
setTimeout( function cb() {
console.log(' Hello ' + that.name);
}, 500);
};
Another option would be an arrow function, as they keep the surrounding context and don't create their own context.
var o = {
fn () {
setTimeout( () => { console.log(this) }, 500)
}
}
var o2 = {
fn () {
setTimeout( function() {
console.log(this === window)
}, 1000)
}
}
o.fn(); // fn() --- arrow function
o2.fn(); // true, is window --- regular function

JS - called method is not a function

I have object:
var devark = {
init: function() {
var obj = this;
obj.assignHandlers();
},
assignHandlers: function() {
var obj = this;
document.getElementById("menu-toggler").onclick = obj.documentFunctions[0];
},
documentFunctions: [
function() {
toggleClass(this, "opened");
}
]
};
on window.load, I am calling the init method. That works fine but when it calls another object method assignHandlers, it throws an error:
[17:54:33.192] TypeError: obj.assignHandlers is not a function
Why is it?
Like #Bergi said, it's a this value issue that can be solved by doing:
window.onload = function () {
devark.init();
};
The difference between both ways is the value of this within the init function. To determine the natural value of this, look at the left-side of the . in a method call, such as obj.someFn();. Here the value of this within someFn will be obj. However, when you do window.onload = devark.init;, you can imagine the handler will later be invoke like window.onload(). Which means the value of this within the onload function, which is really the init function will be window, not devark.
You can also use Function.prototype.bind to bind a specific this value to a function.
window.onload = devark.init.bind(devark);

Prototype Pattern and "this"

I'm trying to create a client-side api for a web control using the Prototype pattern. However I want to make life easier by not having to manage "this".
This is some sample code (i have commented the problematic line):
MyObject = function ()
{
MyObject.initializeBase(this);
this._someProperty = null;
};
MyObject.prototype = {
initialize: function()
{
// Init
},
get_someProperty: function()
{
return this._someProperty;
},
set_someProperty: function(value)
{
this._someProperty = value;
},
doSomething: function ()
{
$('.some-class').each(function ()
{
$(this).click(this.doClick); // this.doClick is wrong
});
},
doClick: function ()
{
alert('Hello World');
}
};
Normally, using the revealing module pattern I would declare a private variable:
var that = this;
Can I do something similar with the Prototype pattern?
You can do the exact same thing you are used to, just do it within the doSomething method:
doSomething: function ()
{
var instance = this;
$('.some-class').each(function ()
{
$(this).click(instance.doClick);
});
},
This approach has nothing to with prototype or not, it's just how to manage context with nested functions. So when a function on a prototype (method) has nested functions within in, you may have to preserve the context this at any of those level if you want to access it in a nested scope.
ES5's Function.prototype.bind() might be an option for you. You could go like
doSomething: function ()
{
$('.some-class').each(function(_, node)
{
$(node).click(this.doClick); // this.doClick is right
}.bind(this));
},
Now, we proxied each event handler by invoking .bind() and as a result, we call it in the context of the prototype object. The caveat here is, you no longer have this referencing the actuall DOM node, so we need to use the passed in arguments from jQuery instead.

Call functions from function inside an object (object literal)

I'm learning to use object literals in JS, and I'm trying to get a function inside an object to run by calling it through another function in the same object. Why isn't the function "run" running when calling it from the function "init"?
var RunApp = {
init: function(){
this.run()
},
run: function() {
alert("It's running!");
}
};
That code is only a declaration. You need to actually call the function:
RunApp.init();
Demo: http://jsfiddle.net/mattball/s6MJ5/
There is nothing magical about the init property of an object, which you happen to have assigned a function to. So if you don't call it, then it won't run. No functions are ever executed for you when constructing an object literal like this.
As such, your code becomes this:
var RunApp = {
init: function(){
this.run()
},
run: function() {
alert("It's running!");
}
};
// Now we call init
RunApp.init();
You can try the following code. It should work:
var RunApp = {
init: function(){
RunApp.run()
},
run: function() {
alert("It's running!");
}
};

Get object in member function callback

I have an object with a method that I’d like to pass to a function as a callback. However, inside the callback, this no longer refers to my object. Why not?
I’m familiar with using a variable to get around the problem when passing a function literal:
var obj = {
a: function () {
var me = this;
console.log(this);
setTimeout(function () {
console.log(this); // Not obj
console.log(me); // This works!
}, 100);
}
};
How can I fix it in this case?
var obj = {
b: function () {
setTimeout(this.callback, 100);
},
callback: function () {
console.log(this); // =(
}
};
Yes, this can be kind of tricky in Javascript. The problem is that its value depends on how you call the function.
obj.callback(); //ok
var f = obj.callback;
f(); //does not look like a method call
//Javascript does not pass the this!
The usual workaround is passing a wrapper callback like you did in b), except that the common name for the me variable is that (you sometimes see self too)
var that = this;
setTimeout( function(){ return that.callback(); }, 300);
The other alternative is to use the bind method from functions
setTimeout( this.callback.bind(this) , 300)
Note that bind is not supported in IE 8 (you might need a shim in that case).
For more:
Maintaining the reference to "this" in Javascript when using callbacks and closures

Categories