This question already has answers here:
JavaScript setInterval and `this` solution
(9 answers)
Referencing "this" inside setInterval/setTimeout within object prototype methods [duplicate]
(2 answers)
How to access the correct `this` inside a callback
(13 answers)
Closed 5 months ago.
I have a problem in regard to setInterval that I can't figure out.
There is the problem with the scope when calling setInterval or timeout from within an object, but still I can't wrap my head around it.
I tried to put my stuff inside an anonymous function, it won't work.
This is basicly my problem, simplified to the bare bones:
function Scenario(){
var ships = [];
this.ini = function(){
for (var i = 0; i < ships.length; i++){
timeoutID1 = setTimeout(ships[i].ding, 1000);
timeoutID2 = setTimeout(ships[i].bing, 1000);
}
}
this.setShips = function(){
var ship = new Ship("ship");
ships.push(ship);
}
function Ship(name){
this.name = name;
this.ding = function(){
intervalID1 = setInterval(function(){
console.log("ding");
}, 500)
}
this.bing = function(){
var _this = this;
intervalID2 = setInterval(function(){
console.log(_this.name);
}, 500)
}
}
this.setShips();
}
var scenario = new Scenario();
scenario.ini();
http://jsfiddle.net/ancientsion/xkwsn7xd/
Basicly, console.log("ding") works, console.log(_this.name) doesn't.
Why?
This is your problem simplified to bare bones:
var ship = {
name: 'Sheep',
ding: function() {
console.log(this.name);
}
}
setTimeout(ship.ding, 1000); // doesn't work correctly
It may help to see another example to understand why the above doesn't work:
var ding = ship.ding;
ding(); // doesn't work either
In JavaScript this depends on how you call your function. ship.ding() will set this to the sheep object. Bare ding() call will set this to the window object.
You can bind the function to the object you want by using .bind() method. (Function.prototype.bind())
var ding = ship.ding.bind(ship);
ding(); // works
ding is now permanently bound to the sheep object. You can use exactly the same approach with setTimeout:
setTimeout(ship.ding.bind(ship), 1000);
By the time setTimeout() gets around to call your method, it only sees the function and not the invocation context (i.e. the object to bind it to); much like this:
var bing = ships[i].bing;
bing(); // inside bing(), this == window
Basically, you need to provide setTimeout() with a prewired method invocation:
var bound_bing = ships[i].bing.bind(ships[i]);
timeoutID2 = setTimeout(bound_bing, 1000);
The "magic" happens with .bind() as it returns a new function that will have this set up properly.
You should define the value _this in Ship function:
function Ship(name){
this.name = name;
this.ding = function(){
intervalID1 = setInterval(function(){
console.log("ding");
}, 500)
}
var _this = this;
this.bing = function(){
intervalID2 = setInterval(function(){
console.log(_this.name);
}, 500)
}
}
Hoist _this out of the bing function.
you should have
_this = this,
this.bing = function() {
intervalID2 = setInterval(function(){
console.log(_this.name);
}, 500)
}
Javascript is imperative so you need to follow the execution path carefully. In function Ship, this points to a Ship object, in function bing, this points to the global scope (window). You need to save a reference to this so that you can refer back to it in these types of functions.
You put ships[i].bing in setTimeout turns out that the caller of bing is not ships[i] but global, so _this is pointing to global actually.
Related
This question already has answers here:
How to access the correct `this` inside a callback
(13 answers)
Closed 1 year ago.
Normally I'd assign an alternative "self" reference when referring to "this" within setInterval. Is it possible to accomplish something similar within the context of a prototype method? The following code errors.
function Foo() {}
Foo.prototype = {
bar: function () {
this.baz();
},
baz: function () {
this.draw();
requestAnimFrame(this.baz);
}
};
Unlike in a language like Python, a Javascript method forgets it is a method after you extract it and pass it somewhere else. You can either
Wrap the method call inside an anonymous function
This way, accessing the baz property and calling it happen at the same time, which is necessary for the this to be set correctly inside the method call.
You will need to save the this from the outer function in a helper variable, since the inner function will refer to a different this object.
var that = this;
setInterval(function(){
return that.baz();
}, 1000);
Wrap the method call inside a fat arrow function
In Javascript implementations that implement the arrow functions feature, it is possible to write the above solution in a more concise manner by using the fat arrow syntax:
setInterval( () => this.baz(), 1000 );
Fat arrow anonymous functions preserve the this from the surrounding function so there is no need to use the var that = this trick. To see if you can use this feature, consult a compatibility table like this one.
Use a binding function
A final alternative is to use a function such as Function.prototype.bind or an equivalent from your favorite Javascript library.
setInterval( this.baz.bind(this), 1000 );
//dojo toolkit example:
setInterval( dojo.hitch(this, 'baz'), 100);
i made a proxy class :)
function callback_proxy(obj, obj_method_name)
{
instance_id = callback_proxy.instance_id++;
callback_proxy.instances[instance_id] = obj;
return eval('fn = function() { callback_proxy.instances['+instance_id+'].'+obj_method_name+'(); }');
}
callback_proxy.instance_id = 0;
callback_proxy.instances = new Array();
function Timer(left_time)
{
this.left_time = left_time; //second
this.timer_id;
this.update = function()
{
this.left_time -= 1;
if( this.left_time<=0 )
{
alert('fin!');
clearInterval(this.timer_id);
return;
}
}
this.timer_id = setInterval(callback_proxy(this, 'update'), 1000);
}
new Timer(10);
I am confused regarding how to work with this in combination with bind. I know the problem stems from this being the global object. Could somebody explain how I could circumvent?
In the constructor I have done this:
var BoundedStartTimer = this.StartTimer.bind(this);
var BoundedStopTimer = this.StopTimer.bind(this);
var BoundedClearTimeString = this.ClearTimeString.bind(this);
BoundedClearTimeString();
The last one works, but the timer does not start when I call BoundedStartTimer();.
I am not sure what I am doing. Below are my declarations:
MemoryGame.prototype.StartTimer = function(){
var playTimeInMilliseconds = 0;
this.timeString = "";
this.timer = window.setInterval(function(){
if(playTimeInMilliseconds >= 1000)
this.timeString = .....
this.handleToTimerText.textContent = this.timeString;
}, 10);
}
MemoryGame.prototype.StopTimer = function(){
clearInterval(this.timer);
}
MemoryGame.prototype.ClearTimeString = function(){
this.handleToTimerText.textContent = "00:000";
}
The issue here is with your setInterval call. setInterval executes functions in the global scope, meaning that this === window which is not ideal. You were right that bind can work here though, just bind the interval callback to this:
window.setInterval(function(){
if(playTimeInMilliseconds >= 1000)
this.timeString = .....
this.handleToTimerText.textContent = this.timeString;
}.bind(this), 10);
// ^ bind this from your MemoryGame instance to the interval callback
So here's the deal.
Every time you see the keyword function, you should think two things.
First is, "oh, this creates a new scope for variables!" Lots of people from Java think this happens with each curly brace { that is not used as an object literal; that's false, it's only a function context (or, now with ES6, there is let and => that do it too.)
The second thing you should think is, "oh, probably this and arguments are going to be different in the new variable scope." That's because these two keywords are especially "magical" in JavaScript.
So when you write:
this.timer = window.setInterval(function () {
if (playTimeInMilliseconds >= 1000) {
this.timeString = .....
}
this.handleToTimerText.textContent = this.timeString;
}, 10);
...you have to see that function as a new variable scope with its own this and arguments.
Now, playTimeInMilliseconds is going to be fine if you don't at any point in this function write var playTimeInMilliseconds, which will get "hoisted" to the top of the function declaration and declare a new variable local to that scope. As long as you never do this, playTimeInMilliseconds will peek at the parent variable scope and find the variable you defined in the outer scope.
However, this.timeString and this.handleToTimerText are not fine because the value of this was declared to be window.
There are two remedial approaches:
In the outer function, capture this into your own variable. Common variable names for this purpose are are me and self. Just write var self = this; in the outer function, then do not write var self in the inner function, then write self.timeString and self.handleToTimerText.
Take the function and explicitly .bind() it. So pull out the function like so:
function updateTimerText() {
if (playTimeInMilliseconds >= 1000) {
this.timeString = .....
}
this.handleToTimerText.textContent = this.timeString;
}
this.timer = setInterval(updateTimerText.bind(this), 10);
Obviously, bind has a lot of power! So don't do crap like this:
var BoundedStartTimer = this.StartTimer.bind(this);
var BoundedStopTimer = this.StopTimer.bind(this);
var BoundedClearTimeString = this.ClearTimeString.bind(this);
BoundedClearTimeString();
I have no idea what scope this stuff is occurring in, but it's probably not in the right scope. Because whatever you're binding as this has to be the instance of new MemoryGame() that you created -- maybe let's say you called it var myMemoryGame = new MemoryGame(); or so; just use myMemoryGame.startTimer() (or whatever) and let the this come naturally. Whenever you write a.b.c.d.e() the this in the context of function e() is magically set to a.b.c.d. Don't reassign it to this unless you know what the heck you're doing and that you have to preserve this in some new context. At the very least be nice to readers and .bind(myMemoryGame) so that it's 100% clear.
Im trying to write some OOP in javascript, and I stumbled upon a problem that I am sure Im going to have later on in my code and wish to deal with it now.
for example take this code:
var FeedClass = function(){
this.init = function(){
this.loadItems();
},
this.loadItems = function(){
var that = this; // heres my problem
function inner(){
that.startLoading();
}
inner();
},
this.startLoading = function(){
alert("started Loading");
}
this.init();
};
var feed = new FeedClass();
the problem is Im going to use a lot of inner functions that will call "this", my code will be messy if I kept writing var that = this inside every scope. Is there another pattern I can use or a way to get around it?
You can use the call method to set the context for the function:
this.loadItems = function(){
function inner(){
this.startLoading();
}
inner.call(this);
},
The apply method works similarly, the difference is how you specify parameters in the call.
You can also use the bind method to set the context of a function. That allows you to bind the context to the function and pass the function reference on to be called later:
this.loadItems = function(){
function inner(){
this.startLoading();
}
var i = inner.bind(this);
i();
},
Note: The bind method is not supported in IE 8 or earlier.
I am a proficient c# programmer, and I have a little bit of experience with HTML and Javascript, but I can't seem to figure out objects.
I am attempting to create a loop, which just keeps updating until the counter reaches 5 then stops the loop. but in the Update method counter = NaN.
This is my exact code.
function LoginScreen() {
this.IntervalID = null;
this.counter = 0;
this.Start = function () {
this.IntervalID = setInterval(this.Update, 60 / 1000);
};
this.Stop = function () {
clearInterval(this.IntervalID);
};
this.Update = function () {
this.counter++;
alert(this.counter);
if (this.counter > 5) this.Stop();
};
}
LS = new LoginScreen();
LS.Start();
The problem is scope of the inner this of your Start(), Stop() and Update() functions. Within those functions, this is referring to the function, and not your LoginScreen object.
To get around these problems, I like to use a little self variable to help with scope issues. That way, whatever is referring to the self variable will use the object. Here's what I mean:
function LoginScreen() {
// add this for scope fun...
var self = this;
this.IntervalID = null;
this.counter = 0;
this.Start = function () {
self.IntervalID = setInterval(self.Update, 60 / 1000); // updated...
};
this.Stop = function () {
clearInterval(self.IntervalID); // updated...
};
this.Update = function () {
self.counter++; // updated...
alert(self.counter); // updated...
if (self.counter > 5) self.Stop(); // updated...
};
}
LS = new LoginScreen();
LS.Start();
Hope this makes sense!
You ran into the dreaded Javascript "this" problem.
Try changing your this.Update code to:
var self = this;
this.Update = function () {
self.counter++;
alert(self.counter);
if (self.counter > 5) self.Stop();
};
But wait there is more
This is a very common mistake in Javascript. I think everyone including me did this at one point. It is fairly easy to understand, though.
What's going on
When you call a function as a method of an object, you set its context to the object. That means that this inside this function will point to your context object.
MyObject = {};
MyObject.someValue = 1;
MyObject.myMethod = function() {
console.log(this.someValue); // This will log "1" to the console, as expected
};
Now comes the tricky part: You can change the context of a function. Let's add some more lines of code here:
MyOtherObject = {};
MyOtherObject.someValue = 2;
MyOtherObject.myMethod = MyObject.myMethod;
When you now call MyOtherObject.myMethod you call the same function as if you would call MyObject.myMethod, but in one case this points to MyObject and in the other case it points to MyOtherObject.
Now, when you call setInterval(MyObject.myMethod, 1000), the functions context will be set to window (technically, setInterval calls window.setInterval) and since window does not have a property called someValue, it will just be undefined.
How to fix it
To fix this, you can create another reference to your LoginScreen object. It will always point to the same object inside the entire scope, unless you change it. You can then use it as an alternative to this without worrying about the context.
function LoginScreen() {
this.IntervalID = null;
this.counter = 0;
// Create another reference to "this"
var self = this;
this.Start = function () {
// The function behind "this.Update" will always be called with "window"
// as its context.
this.IntervalID = window.setInterval(this.Update, 60 / 1000);
};
this.Stop = function () {
// This function is called as "self.Stop", therefore "this" points to
// "self", which points to the right object, so you can use "this".
clearInterval(this.IntervalID);
};
this.Update = function () {
// Inside this function, "this" points to "window".
// You therefore have to use the previously declared "self"
// to point to the right object.
self.counter++;
alert(self.counter);
if (self.counter > 5) self.Stop();
};
}
LS = new LoginScreen();
LS.Start();
Further reference
this - Mozilla Developer Network Another explanation of the beviour of this in JavaScript, along with some functions to control it.
I have the following code example to use an object that receives the action from the callback. Doesn't seem like this is a good design pattern. Or is it?
When setTimeOut() fires on the function after 1 second, it uses the objInstance global variable (DOM scope) to access the ClassExample object instance. Can someone recommend a better way to utilize callbacks within an object oriented design?
The whole idea is so I can use the callback to update data within my object instance (increment a variable for example).
function ClassExample{
this.initiate = function() {
setTimeOut(objInstance.afterTimeOut,1000); //using the objects global handle
}
this.afterTimeOut = function() {
alert("Received!");
}
}
var objInstance = new ClassExample(); //instance
objInstance.initiate();
No, you're not. You'll want to do this:
this.initiate = function() {
setTimeOut(objInstance.afterTimeOut,1000); //using the objects global handle
}
Now, if "afterTimeout" needs the proper object context, you could do this:
this.initiate = function() {
var instance = this;
setTimeout(function() { instance.afterTimeOut(); }, 1000);
}
OK well you changed the question considerably with that little edit :-) If I were you, I'd just do this (like my original second example):
this.initiate = function() {
var instance = this;
setTimeout(function() { instance.afterTimeOut(); }, 1000);
}
Then you don't need any ugly global variables around at all.
edit — Stackoverflow user #Christoph comments that this isn't particularly pretty. One thing that might help would be to use a "bind" facility, as provided by newer browsers natively (as a method on the Function prototype) or by some libraries (Prototype or Functional for example). What "bind" lets you do is create a little wrapper function like I've got above:
this.initiate = function() {
setTimeout(this.afterTimeOut.bind(this), 1000);
}
That call to "bind" returns a function that is effectively the same sort of thing as the little wrapper I coded explicitly in the example.
function ClassExample{
this.afterTimeOut = function() {
alert("Received!");
}; // Don't forget these
setTimeOut(afterTimeOut, 1000); // Don't use () if you're passing the function as an argument
}
var objInstance = new ClassExample(); //instance
That way you don't need the initiate() method.
If you really want the initiate() method, I'd do it like this:
function ClassExample{
var self = this;
self.afterTimeOut = function() {
alert("Received!");
};
self.initiate = function() {
setTimeOut(self.afterTimeOut, 1000);
};
}
var objInstance = new ClassExample(); //instance
objInstance.initiate();
This is how I'd do it to allow timer reuse and minimize the number of closures:
function Timer(timeout, callback) {
this.timeout = timeout;
this.callback = callback;
}
Timer.prototype.run = function(thisArg /*, args... */) {
var argArray = Array.prototype.slice.call(arguments, 1);
var timer = this;
setTimeout(function() {
timer.callback.apply(thisArg, argArray);
}, timer.timeout);
};
var timer = new Timer(1000, alert);
timer.run(null, 'timer fired!');
And just for fun, a golfed version which is functionally equivalent, but replaces the object with a closure:
function delay(func, timeout) {
return function() {
var self = this, args = arguments;
setTimeout(function() { func.apply(self, args); }, timeout);
};
}
delay(alert, 1000).call(null, 'timer fired!');
You are right it is not the optimal way of doing what you are aiming for. however i have to wonder why you need to break the callstack as part of the initiation, it seems very academic.
apart from that if i had to do that, i'd probably use a closure like so:
function ClassExample{
this.initiate = function() {
setTimeOut((function(self) { return function() { self.afterTimeout();}})(this),1000); //using the objects global handle
}
this.afterTimeOut = function() {
alert("Received!");
}
}
var objInstance = new ClassExample(); //instance
objInstance.initiate()
this.initiate = function() {
var instance = this;
setTimeOut(function() {
instance.afterTimeOut();
}, 1000);
};
By saving this to a local variable, you can avoid using the global handle at all. Also this prevent the afterTimeout() from losing it's this.
Building on Znarkus answer...
I really don't know in which environment his code is running but for me the first approach just do not works. I got: 'ReferenceError: afterTimeOut is not defined'...
The second one, nevertheless, is really cool... I just changed setTimeOut for setTimeout (using lowercase 'o') and included parenthesis after the class name definition turning the first line of code into 'function ClassExample(){'; solved my problem.
My snippet of example code:
Oop with private behaviour, intern callback calling and etc.
function MyTry (name){
// keep this object pointer... that's the trick!
var self = this;
// create private variable
var d = new Date()toJSON().slice(0, 10);
// create a private function
function getName(){return name}
// create public access method
self.hello = function(){alert('Hello '+getName()+'!\nToday is: '+d)}
// note instance method hello passed as a callback function!
self.initiate = function(){setTimeout(self.hello, 3000)}
}