I am using John Resig's Simple JavaScript Inheritance and have run into an issue where I am losing what 'this' refers to. Using this code:
var Runner = Class.extend({
init: function() {
this.update();
if(!this.interval) {
this.interval = setInterval(this.update, this.period * 1000);
}
},
stop: function() {
clearInterval(this.interval);
},
update: function() {
this.success()
},
success: function(){
}
});
var SubRunner = Runner.extend({
update: function() {
this._super();
},
success: function(){
alert('sub runner success');
}
});
Running p = new SubRunner() works as I would expect and alerts sub runner success the first time. After the first run through it then tries to run the success function on the wrong 'this' (window).
I know Prototype gives you a bind function so that you can pass the context to the function but I haven't had any luck in doing something similar here. Does anyone have a starting point to figuring this out?
Thanks!
The problem is when you pass this.update to the setInterval function. In Javascript, the "this" depends on wether you call the function using dot notation, and functions will not remember where they came from if you pass them as callbacks or store them in a variable.
You can either add a wrapper function
var that = this;
setTimeout(function(){ that.update() }, this.perios*1000)
or you can use the bind method if its available in your browser (or you can use the similar function in Prototype).
setTimeout(this.update.bind(this), this.period*1000)
When you pass this.update to setInterval you lose the context.
The simplest solution is to do
var that = this;
this.interval = setInterval(function() { that.update() }, this.period * 1000);
this.interval = setInterval(this.update, this.period * 1000);
When setTimeout calls a function it calls it in the global scope (it sets this to window).
You need to pass a function that calls this.update.
var self = this;
this.interval = setInterval(function(){
self.update();
}, this.period * 1000);
Related
So I can't quite figure out why the variable this.tasks becomes undefined inside of the add event listener I have inside of my goal object. I have a feeling it might have something to do with asynchronous programming(which I still don't fully understand). Sorry I'm a bit of a JS noob, but if you guys could explain to me what I'm doing wrong and what might be a better solution that would be awesome! Thanks.
function Goal(name) {
this.gDiv = document.createElement('div');
this.name = name || "goal";
this.tasks = document.createElement('ul');
//Sets the styling and content and adds it to the parent element
this.initialize = function() {
this.gDiv.className = "default";
this.gDiv.setAttribute("id", this.name);
this.gDiv.innerHTML = this.name;
elem.appendChild(this.gDiv);
this.gDiv.parentNode.insertBefore(this.tasks, this.gDiv.nextSibling);
this.tasks.style.display = "none";
};
//Creates a list underneath the a dive associated with the Goal object
this.addTask = function(task) {
var newLi = document.createElement('li');
newLi.innerHTML = task;
this.tasks.appendChild(newLi);
};
this.gDiv.addEventListener('click', function(){
alert(this.tasks);
});
}
Thank you guys! You all answered my question! I'd been scratching my head at this for a while. Kudos to you all!
The scope changes when you enter that anonymous closure and 'this' changes. You can hack around it by doing
var self = this;
And then using self in place of this (eg):
function Goal(name) {
var self = this;
/* ... */
this.gDiv.addEventListener('click', function(){
alert(self.tasks);
});
If you're using jQuery you could do something nicer:
this.gDiv.addEventListener('click', $.proxy(function() {
alert(this.tasks);
}, this));
Either way works just fine.
EDIT: In ES6, arrow functions can be used instead as they don't bind their own "this", so it becomes even simpler:
this.gDiv.addEventListener('click', () => {
alert(this.tasks);
});
Here is a comparison of some methods (including your problem), to give you a taster, and to try and explain things a little.
// This is the problem that you have,
// where `this` inside the anonymous function
// is a different scope to it's parent
function Test1(something) {
// `this` here refers to Test1's scope
this.something = something;
setTimeout(function() {
// `this` here refers to the anonymous function's scope
// `this.something` is `undefined` here
console.log(this.something);
}, 1000);
};
new Test1('Hello');
// This solution captures the parent `this` as `test2This`,
// which can then be used inside the anonymous function
function Test2(something) {
var test2This = this;
this.something = something;
setTimeout(function() {
console.log(test2This.something);
}, 1000);
}
new Test2('World');
// This solution captures `this` as `test3This` in an `IIFE closure`
// which can then be used in the anonymous function
// but is not available outside of the `IIFE closure` scope
function Test3(something) {
this.something = something;
(function(test3This) {
setTimeout(function() {
console.log(test3This.something);
}, 1000);
}(this));
}
new Test3('Goodbye');
// This method requires that you load an external library: jQuery
// and then use it's `$.proxy` method to achieve the basics of
// Test3 but instead of being referred to as `test3This` the
// outer scope `this` becomes the inner scope `this`
// Ahh, that's much clearer?
function Test4(something) {
this.something = something;
setTimeout($.proxy(function() {
console.log(this.something);
}, this), 1000);
}
new Test4('Mum');
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
// This is approximately what jQuery's `$.proxy` does
// but without having to load the whole library
function Test5(something) {
this.something = something;
setTimeout((function(func, context) {
return function() {
func.call(context);
};
}(function() {
console.log(this.something);
}, this)), 1000);
}
new Test5('Dad');
// Lets create the proxy method as a reuseable
function proxy(func, context) {
var args = Array.prototype.slice.call(arguments, 2);
return function() {
return func.apply(
context,
args.concat(Array.prototype.slice.call(arguments))
);
};
}
// and now using it
function Test6(something) {
this.something = something;
setTimeout(proxy(function() {
console.log(this.something);
}, this), 1000);
}
new Test6('Me want cookies');
Then we have Function#bind
function Test7(something) {
this.something = something;
setTimeout(function() {
// `this` was bound to the parent's `this` using bind
console.log(this.something);
}.bind(this), 1000);
};
new Test7('Num num');
<script src="https://cdnjs.cloudflare.com/ajax/libs/es5-shim/4.5.9/es5-shim.min.js"></script>
And most recently ES2015 Arrow functions
function Test8(something) {
this.something = something;
setTimeout(() => console.log(this.something), 1000);
};
new Test8('Whoop');
In ES6, arrow functions were introduced, which do not bind their own this.
MDN for reference.
So creating an anonymous function using the arrow syntax is probably the easiest way to overcome this issue nowadays. It is supported by all major browsers currently, except IE.
the keyword 'this' changes in it's meaning for an event handler against a constructor
please refer to the MDN
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this#As_a_DOM_event_handler
I have defined an API object:
function API() {
var self = this;
return {
getRandomArticle: function() {
$.getJSON("http://en.wikipedia.org/w/api.php?action=query&generator=random&grnnamespace=0&prop=extracts&exchars=50000&format=json&callback=?", function (data) {
for(var id in data.query.pages) {
console.log(data.query.pages[id].extract);
}
});
},
repeatAPICall: function() {
self.getRandomArticle();
console.log(self);
setTimeout(self.repeatAPICall, 5000);
}
}
}
And then I instantiated the API object with window.test = new API();.
When I head over to Chrome Dev tools and call window.test.repeatAPICall(), it works once, then it fails and says TypeError: Object #<API> has no method 'getRandomArticle'
I suspect that somehow the recursive call is behaving differently than I intended, what am I doing wrong?
Working code:
function API() {
var self = this;
self.getRandomArticle = function() {
$.getJSON("http://en.wikipedia.org/w/api.php?action=query&generator=random&grnnamespace=0&prop=extracts&exchars=50000&format=json&callback=?", function (data) {
for(var id in data.query.pages) {
console.log(data.query.pages[id].extract);
}
});
},
self.repeatAPICall = function() {
self.getRandomArticle();
console.log(self);
setTimeout(self.repeatAPICall, 5000);
}
return this;
}
window.test = new API();
Now you've fixed "self" vs. "this" the next change is to use
self.getRandomArticle= ...
self.repeatAPICall=...
and then just return self/this. That should work. Right now, you have two objects - this and the one you return.
Your main issue is the passing of this.repeatAPICall into setTimeout. When you call a method in JavaScript, the this keyword points to the object that called it:
var something = {
foo : function(){
return this;
}
};
something.foo(); //=> something
However, if you assign the function to a different variable, the context changes (to the global window object):
var something = {
foo : function(){
return this;
}
};
something.foo(); //=> something
var bar = something.foo;
bar(); //=> window
This is what's happening above; you're passing a reference to the function to setTimeout, which is then losing the correct context.
Instead, you need to pass in a function which keeps the context; you could use the self = this statement like so:
repeatAPICall: function() {
self = this;
self.getRandomArticle();
setTimeout(function(){
self.repeatAPICall();
}, 5000);
This creates anonymous function which remembers the state of the self object (this is how JavaScript variable scope works). When that function gets called, it can then call repeatAPICall as a method on that object, rather than as a function with no context.
The accepted answer avoids having to do this (each method can access self), but hopefully this explains why it wasn't working.
I would like to do something like this:
function end(){ console.log(this); } // <-- the problem is here with `this`
eval('var a = 0; setTimeout(function(){ a = 10; end(); }, 2000)');
which 2 seconds later should output:
{ "a" : 10 }
Is this somehow possible?
Yes:
function end(){ console.log(this); }
eval('var a = 0, self = this; setTimeout(function(){ a = 10; end.call(self); }, 2000)');
Note that I set a variable, self, to be this, and then use Function#call when calling end, which allows us to set a specific value for this during the call. This works because the anonymous function passed to setTimeout has a reference to the execution context in which it was created and all variables within that, and so has access to self (and a).
If there's not a really good reason for using eval (and I don't see one here), I wouldn't, just do this:
function end(){ console.log(this); }
var a = 0, self = this; setTimeout(function(){ a = 10; end.call(self); }, 2000);
You can also create a second function that, when called, turns around and calls end with the right this value. This is called binding, and is facilitated by the ES5 Function#bind function:
function end(){ console.log(this); }
var a = 0, boundEnd = end.bind(this); setTimeout(function(){ a = 10; boundEnd(); }, 2000);
Since you're using NodeJS, you're using V8, which has Function#bind. (If you were doing this in a browser, you'd have to be careful to provide a shim for bind if you needed to support older browsers.)
Furthering my experiments in proper Javascript, I'm trying to run one method (SayHello) from another (WaitAndSayHello). As a callback is involved, I have used bind to ensure that 'this' in each method refers to the object.
pretendThingConstructor = function (greeting) {
this.greeting = greeting;
this.SayHello = function() {
console.log(this.greeting); // Works
};
this.WaitAndSayHello = function() {
setTimeout(function() {
console.log(this)
this.SayHello() // Fails
}, 500);
}
this.WaitAndSayHello.bind(this); // This bind() doesn't seem to work
}
var pretend_thing = new pretendThingConstructor('hello world');
pretend_thing.SayHello();
pretend_thing.WaitAndSayHello();
The code prints 'hello world', then fails with 'Object # has no method 'SayHello'' the second time around. I can see, from the console.log, that 'this' is referring to the event. However shouldn't have using bind() fixed this?
How can I make the bind() work?
Additionally, I'd like to do this cleanly: ie, without referring to the name of the object in multiple places.
You can't "late call" .bind(). You need to invoke it at function declaration time, like
this.WaitAndSayHello = function() {
setTimeout(function() {
console.log(this)
this.SayHello() // Fails
}, 500);
}.bind(this)
Furthermore, the anonymous function you pass into setTimeout() creates a new context and therefore, has its own this context value.
You either need to hold a reference to the "outer this" in a variable like
this.WaitAndSayHello = function() {
var self = this;
setTimeout(function() {
console.log(self)
self.SayHello() // succeeds
}, 500);
}.bind(this)
or use .bind() again, like
this.WaitAndSayHello = function() {
setTimeout(function() {
console.log(this)
this.SayHello() // succeeds
}.bind(this), 500);
}.bind(this)
you should use:
this.WaitAndSayHello.call(this);
or
this.WaitAndSayHello.apply(this);
the difference between apply and call is the way you would pass arguments to the called function: imagine WaitAndSayHello received some args:
this.WaitAndSayHello = function(toWho, helloMessage){
...
}
with call, you would pass the arguments after the context, as you were invoking the function normally:
this.WaitAndSayHello.call(this, 'Bob', 'Hello');
with apply, you would have to pass the args as an array:
this.WaitAndSayHello.apply(this, ['Bob', 'Hello']);
Edit
Sorry, i read your code wrong, #jAndy's anwser is really the right one, however, to use my logic you could do something like:
this.WaitAndSayHello = function() {
setTimeout.call(this, function() {
console.log(this)
this.SayHello() // Fails
}, 500);
}
I am trying to use setTimeout() inside a class function in JavaScript. The setTimeout() is supposed to trigger another method in the same Class, so the function I am passing it is written as window.setTimeout("this.anotherMethod", 4000). That bring the problem: this references the calling Object, in the case of setTimeout() it is window. How can I use enclosures to return a reference to the Class Object itself?
myObject = function(){
this.move = function(){
alert(this + " is running");
}
this.turn = function(){
alert(this + " is turning");
}
this.wait = function(){
window.setTimeout("this.run" ,(1000 * randomNumber(1,5)));
}
this.run = function(){
switch(randomNumber(0,2)){
case 0:
this.move();
break;
case 1:
this.turn();
break;
case 2:
this.wait();
}
}
}
You can do this:
var that = this;
setTimeout(function () {
that.doStuff();
}, 4000);
You can also bind for more succinct code (as originally pointed out by #Raynos):
setTimeout(this.doStuff.bind(this), 4000);
bind is a standard library function for exactly this coding pattern (ie capturing this lexically).
You can also bind a function to scope.
setTimeout(this.run.bind(this) ,(1000 * randomNumber(1,5)));
Be warned Function.prototype.bind is ES5
this can be problematic in javascript, as you've discovered.
I usually work around this by aliasing this inside the object so that I can use the alias whenever I need a reference back to the containing object.
MyObject = function ()
{
var self = this;
// The rest of the code goes here
self.wait = function(){
window.setTimeout(self.run ,(1000 * randomNumber(1,5)));
}
}
this.wait = function(){
var self = this;
window.setTimeout(function() { self.run() } ,(1000 * randomNumber(1,5)));
}
So you store the reference to the object you're calling .run on in a local variable ('self').
class A{
setTimeout(()=>{
// here this != undefined because of arrow function
},500);
}
this is sensitive to the context in which it is called. When you pass a string to setTimeout then that is evaled in a completely different context.
You need to preserve the current value of this (by copying it to a different variable) and maintain the scope (by not using (implied) eval).
this.wait = function(){
var self = this;
setTimeout(function () { self.run() },
(1000 * randomNumber(1,5))
);
}
At the top of your main myObject make a new reference to the current value of this:
var self = this;
and then create a closure for your timer callback that uses that new reference instead of the global object that setTimeout will use as the default context in callbacks:
setTimeout(function() {
self.run();
}, 4000);
var timeoutID = window.setTimeout(func, delay, [param1, param2, ...]);
inside func, this always refer to the global object. you can pass in the current object into func,
var timeoutID = window.setTimeout(func, delay, this);
function func(that) {...}
unfortunately it does NOT work in IE
Note that passing additional parameters to the function in the first syntax does not work in Internet Explorer.
you can just use the arrow function syntax:
setTimeout(() => {
this.doStuff();
}, 4000);
Have you tried;
window.setTimeout("myObject.run" ,(1000 * randomNumber(1,5)));
You can use this code instead, which works in all modern browsers -
setTimeout(function(thisObj) {thisObj.run();},1000,this);
Ref: http://klevo.sk/javascript/javascripts-settimeout-and-how-to-use-it-with-your-methods/
Shorter way. Without anonymous func.
var self = this;
setTimeout(self.method, 1000);
It is not recommended to use setTimeout or setInterval using strings
setTimeout("myFunction()", 5000);
//this is the same as
setTimeout(function(){ eval("myFunction()"); }, 5000)); //<-- eval == BAD
Ran into a more complex situation...class A has a member of type B and a method that calls setTimeout which calls a method on class B. Solved as follows:
class A {
constructor(b) {
this.b = b;
}
setTimer(interval) {
setTimeout(this.b.tick.bind(this.b), interval);
}
}
class B {
constructor(name){
this.name = name;
this.ele = window.document.getElementById('B');
}
tick() {
console.log(this);
this.ele.innerText += ' ' + this.name;
}
}
Which bound A.b to this within B.tick and worked.
Here's a fiddle with bind: https://jsfiddle.net/jrme9hyh/
And one without bind which fails: https://jsfiddle.net/2jde8tq3/