Class variables in JavaScript and setInterval - javascript

Since I need to pass an anonymous function to setInterval if I want parameters, I tried using the below code. Originally I had it calling this.countUp, but as that returned NaN I did some reading and found the .call(this) solution on SO. However, when I combined that with the anonymous function (which I admit I'm a bit foggy on), I'm now getting TypeError: this.countUp is undefined in Firebug.
I suppose I don't need to make count accessible, nor the playBeep method, but let's pretend I wanted to so that I can understand what I'm doing wrong with this code.
function workout() {
var beep = new Audio("beep1.wav");
this.timerWorkout; //three timers in object scope so I can clear later from a different method
this.timerCounter;
this.timerCoolDown;
this.count = 0;
this.startWorkout = function() {
alert(this.count);
this.timerWorkout = setTimeout(this.playBeep, 30 * 1000); //workout beep - 30 seconds
this.timerCounter = setInterval(function() {this.countUp.call(this)}, 1000); //on screen timer - every second
}
this.startCoolDown = function() {
this.timerCoolDown = setTimeout(this.playBeep, 10 * 1000); //cooldown beep - 10 seconds
}
this.playBeep = function() {
beep.play(); //plays beep WAV
}
this.countUp = function() {
this.count++;
document.getElementById("counter").innerHTML = this.count;
}
}
var workout1 = new workout()

Inside startWorkout use bind(this) :
this.timerCounter = setInterval(function() {this.countUp()}.bind(this), 1000);

What happens is setInterval is changing the value of this inside the function you provide for it to call. You need to store this in a separate variable to prevent it from getting overridden.
function workout() {
var self = this;
// ...
this.startWorkout = function() {
alert(this.count);
this.timerWorkout = setTimeout(self.playBeep, 30 * 1000); // this method works
this.timerCounter = setInterval(function() {self.countUp}, 1000); // so does this one
}
}

The reason that the variable scope in js is limited on function. So when you are trying to use this inside a nested function, you get a link to another object. Create a variable var that = this; into a higher-level function, and then use it in any nested function that would refer you to the correct context.

Related

How can I end a requestanimationFrame with a function? [duplicate]

I'm trying to cancel a requestAnimationFrame loop, but I can't do it because each time requestAnimationFrame is called, a new timer ID is returned, but I only have access to the return value of the first call to requestAnimationFrame.
Specifically, my code is like this, which I don't think is entirely uncommon:
function animate(elem) {
var step = function (timestamp) {
//Do some stuff here.
if (progressedTime < totalTime) {
return requestAnimationFrame(step); //This return value seems useless.
}
};
return requestAnimationFrame(step);
}
//Elsewhere in the code, not in the global namespace.
var timerId = animate(elem);
//A second or two later, before the animation is over.
cancelAnimationFrame(timerId); //Doesn't work!
Because all subsequent calls to requestAnimationFrame are within the step function, I don't have access to the returned timer ID in the event that I want to call cancelAnimationFrame.
Looking at the way Mozilla (and apparently others do it), it looks like they declare a global variable in their code (myReq in the Mozilla code), and then assign the return value of each call to requestAnimationFrame to that variable so that it can be used any time for cancelAnimationFrame.
Is there any way to do this without declaring a global variable?
Thank you.
It doesn't need to be a global variable; it just needs to have scope such that both animate and cancel can access it. I.e. you can encapsulate it. For example, something like this:
var Animation = function(elem) {
var timerID;
var step = function() {
// ...
timerID = requestAnimationFrame(step);
};
return {
start: function() {
timerID = requestAnimationFrame(step);
}
cancel: function() {
cancelAnimationFrame(timerID);
}
};
})();
var animation = new Animation(elem);
animation.start();
animation.cancel();
timerID; // error, not global.
EDIT: You don't need to code it every time - that's why we are doing programming, after all, to abstract stuff that repeats so we don't need to do it ourselves. :)
var Animation = function(step) {
var timerID;
var innerStep = function(timestamp) {
step(timestamp);
timerID = requestAnimationFrame(innerStep);
};
return {
start: function() {
timerID = requestAnimationFrame(innerStep);
}
cancel: function() {
cancelAnimationFrame(timerID);
}
};
})();
var animation1 = new Animation(function(timestamp) {
// do something with elem1
});
var animation2 = new Animation(function(timestamp) {
// do something with elem2
});

Is there a way to cancel requestAnimationFrame without a global variable?

I'm trying to cancel a requestAnimationFrame loop, but I can't do it because each time requestAnimationFrame is called, a new timer ID is returned, but I only have access to the return value of the first call to requestAnimationFrame.
Specifically, my code is like this, which I don't think is entirely uncommon:
function animate(elem) {
var step = function (timestamp) {
//Do some stuff here.
if (progressedTime < totalTime) {
return requestAnimationFrame(step); //This return value seems useless.
}
};
return requestAnimationFrame(step);
}
//Elsewhere in the code, not in the global namespace.
var timerId = animate(elem);
//A second or two later, before the animation is over.
cancelAnimationFrame(timerId); //Doesn't work!
Because all subsequent calls to requestAnimationFrame are within the step function, I don't have access to the returned timer ID in the event that I want to call cancelAnimationFrame.
Looking at the way Mozilla (and apparently others do it), it looks like they declare a global variable in their code (myReq in the Mozilla code), and then assign the return value of each call to requestAnimationFrame to that variable so that it can be used any time for cancelAnimationFrame.
Is there any way to do this without declaring a global variable?
Thank you.
It doesn't need to be a global variable; it just needs to have scope such that both animate and cancel can access it. I.e. you can encapsulate it. For example, something like this:
var Animation = function(elem) {
var timerID;
var step = function() {
// ...
timerID = requestAnimationFrame(step);
};
return {
start: function() {
timerID = requestAnimationFrame(step);
}
cancel: function() {
cancelAnimationFrame(timerID);
}
};
})();
var animation = new Animation(elem);
animation.start();
animation.cancel();
timerID; // error, not global.
EDIT: You don't need to code it every time - that's why we are doing programming, after all, to abstract stuff that repeats so we don't need to do it ourselves. :)
var Animation = function(step) {
var timerID;
var innerStep = function(timestamp) {
step(timestamp);
timerID = requestAnimationFrame(innerStep);
};
return {
start: function() {
timerID = requestAnimationFrame(innerStep);
}
cancel: function() {
cancelAnimationFrame(timerID);
}
};
})();
var animation1 = new Animation(function(timestamp) {
// do something with elem1
});
var animation2 = new Animation(function(timestamp) {
// do something with elem2
});

Class variable returns undefined in JavaScript

I'm new to JavaScript. Hence this problem is a bit confusing. I'm trying to simply define a counter and increment it in a class method but its not behaving as I expect it to. Specifically console.log(this.tick_count); prints undefined.
JavaScript:
function Game() {
this.fps = 50;
this.ticks = 3;
this.current_time = (new Date).getTime();
this.draw_object = document.getElementById('game_canvas').getContext('2d');
this.tick_count = 0;
}
Game.prototype.update = function (time) {
this.current_time = time;
}
Game.prototype.draw = function () {
this.draw_object.fillRect(10, 10, 55, 50);
}
Game.prototype.run = function () {
self.setInterval(this.tick, 1000 / (this.fps * this.tick));
}
Game.prototype.tick = function () {
this.tick_count++;
console.log(this.tick_count);
}
function start_game() {
var game_object = new Game();
game_object.run();
}
HTML:
<body onload="start_game()">
<canvas id="game_canvas" width="1024" height="1024"></canvas>
</body>
Coming from a Python background I find this behavior strange. How should I set up my class variables correctly?
This is what is happening.
Essentially you tick function is no longer running in the context of your game_object object. This might sound odd coming from a Python background but basically the this object is set to something else.
So what is it set to? Easy, the window object, how do we know this? Because setInterval's context is the window object.
Moving example as code will not format correctly below
Bind Example
setInterval(this.tick.bind(this), 1000 / (this.fps * this.tick)); //Native (JS v1.8+)
$.proxy(this.tick, this); //jQuery
_.bind(this.tick, this); //underscore / lodash
Explicit context example
Game.prototype.run = function () {
var _this = this;
setInterval(function() {
//So what is this at the moment? window.
//Luckily we have a reference to the old this.
_this.tick();
}, 1000 / (this.fps * this.tick));
};
You can get around this two ways.
Bind your function to the object you want it to be on Bind JS v1.8 (Seeing as you're using canvas that shouldn't be an issue.
Invoke the method explicitly with its context. (See above)
Try
setInterval(this.tick.bind(this), 1000 / (this.fps * this.tick));
// without "self"
Thanks to PSL and TJ Crowder
This will work:
setInterval(this.tick.bind(this), 1000 / (this.fps * this.tick));
As will this:
var self = this;
setInterval(function () {
self.tick();
}, 1000 / (this.fps * this.tick));
Even though this has been answered I think you need to understand what this refers to. See this answer for more details.
If you would like to use closures instead of bind you can limit the scope by calling a function that returns a function (outside of the currently running function). This is so you can minimise the amount of variables that will be available to the closure. Sounds complicated but with a minor adjustment to your code you can do it:
Game.prototype.run = function () {
//best not to define a closure here because the next variable
//will be available to it and won't go out of scope when run
//is finished
var memoryEatingVar=new Array(1000000).join("hello world");;
//note that this.tick(this) is not passing this.tick, it's invoking
//it and the return value of this.tick is used as the callback
//for the interval
setInterval(this.tick(this), 1000 / (this.fps * this.tick));
}
//note the "me" variable, because the outer function Game.prototype.tick
//returns a inner function (=closure) the me variable is available in
//the inner function even after the outer function is finished
Game.prototype.tick = function (me) {//function returning a function
return function(){
me.tick_count++;
console.log(me.tick_count);
}
}

setInterval cannot be used into other functions

After solving a problem about how to display data of an array each XXX seconds with setIterval funcion with the code:
var iterations = 0,
data = ['a','bbbbbbbb','c'],
interval = setInterval(foo, 4000);
function foo() {
console.log(data[iterations]);
iterations++;
if (iterations >= 4){
clearInterval(interval);
}else if(iterations == 1){
//we want to make time longer in this iteration.
clearInterval(interval);
interval = setInterval(foo, 8000);
}
}
I want to clean things and capsulate all this into a function. Problem is, when I insert global vars into a function, foo says all vars are unknown so I've to pass them as attrs. Also, I've changed interval to this in order to detect it, but now it does not work. Displays all data at the same time, seems to ignore setIterval
runTest();
function runTest(){
var iterations = 0,
data = [1,2,3,4,5],
maxIterations = 5,
interval = setInterval(foo(iterations,data,maxIterations),4000);
}
function foo(iterations,data,maxIterations){
var sentence = data[iterations];
var div = d3.select('div').append('p').text(sentence);
iterations++;
if (iterations >= maxIterations){
clearInterval(this);
}else{
clearInterval(this);
interval = setInterval(foo(iterations,data,maxIterations),4000);
}
}
If I set a breakpoint on Chrome debugger flow app seems to be right.
Do you know where the problem is? Thanks!
setInterval needs function as parameter (or a function reference). However, you are passing the result of the function call to foo.
You probably want something like
var iterations = 0,
data = [1,2,3,4,5],
maxIterations = 5,
interval = setInterval(function() { foo(iterations,data,maxIterations); }, 4000);
Here you are passing a function which in turn calls foo with all necessary parameters.
The problem is that you declare the variable 'interval' for the first time inside a function. The variable is not global but localized to that function.
it will work if you first declare all the variables you want to have global, outside any functions.
var iterations, maxIterations, data, interval;
runTest();
function runTest(){
iterations = 0,
data = [1,2,3,4,5],
maxIterations = 5,
interval = setInterval(foo(iterations,data,maxIterations),4000);
}
Be aware that the function foo(), might not change the values of the global variables since you choose the same names for the local variables.

Javascript object to execute delayed loops

I want to have multiple numbers on my web page "spin up" as the page loads, giving an impression like a fruit machine.
This involves a simple function with a delayed loop. The way to do this seems to be to use setTimeout recursively. This works fine for just one number on the page.
However for multiple numbers spinning at the same time, each needs its own spinner object. I used prototypes like this:
var Spinner = function(id){
this.element = $('#' + id);
this.target_value = this.element.text()
this.initial_value = this.target_value - 30;
};
Spinner.prototype.spinUp = function() {
loop(this.element);
function loop(element) {
element.html(this.initial_value += 1);
if (this.initial_value == this.target_value) {
return;
};
clr = setTimeout(loop(element), 30); // 30 millisecond delay
};
};
var Spinner1 = new Spinner('number')
Spinner1.spinUp();
However putting a recursive function inside the prototype method causes a big crash. Can you see a way around this?
Many thanks!
Derek.
A couple of issues:
loop() is not how you pass a function, it's how you invoke a function.
You are not calling the function as a method of the object
Try this:
Spinner.prototype.spinUp = function() {
var loop = function() {
this.element.html(this.initial_value += 1);
if (this.initial_value == this.target_value) {
return;
};
setTimeout(loop, 30); // 30 millisecond delay
}.bind(this); //Just flat out bind the function to this instance so we don't need to worry about it
loop();
};
Demo http://jsfiddle.net/KAZpJ/
When you say:
clr = setTimeout(loop(element), 30);
you are "calling" the function (then and there), and passing the value it returns as the first parameter to setTimeout(..).
You would want an anonymous function doing that job:
setTimeout(function(){loop(element);}, 30);

Categories