Using setTimeout() within a JavaScript class function

2024/2/27 8:38:39

Is it possible to use setTimout() within a JavaScript object?

Currently the animation method call is running once, it seems that the setTimeout() isn't doing its job. I have managed to get it working, but in a really hackish method of having a function outside of the class which uses the setTimeout. I'd like to make the animation loop a job for the AnimationManager class. If you can see any bad practice, or where i'm going wrong.. please give me a heads up!

JavaScript:

var AnimationManager = function(canvas)
{this.canvas = canvas;this.canvasWidth = canvas.width();this.canvasHeight = canvas.height();this.ctx = canvas.get(0).getContext('2d');this.running = true;this.start = function start(){this.running = true;this.animate();}/** Allow the animations to run */this.run = function run(){this.running = false;} /** Stop the animations from running */    this.stop = function stop(){this.running = false;}this.animate = function animate(){if(this.running){this.update();this.clear();this.draw();}setTimeout(this.animate, 40); //25 fps}/** Update all of the animations */this.update = function update(){for(var i in shapes){shapes[i].moveRight();}}/** Clear the canvas */this.clear = function clear(){      this.ctx.clearRect(0,0, this.canvasWidth, this.canvasHeight);  }/** Draw all of the updated elements */this.draw = function draw(){       for(var i in shapes){this.ctx.fillRect(shapes[i].x, shapes[i].y, shapes[i].w, shapes[i].h);}}
}

JavaScript within the index page, which demonstrates how i'd like the AnimationManager to work:

<script type="text/javascript">$(document).ready(function() {var canvas = $('#myCanvas');var am = new AnimationManager(canvas);am.start();//If true play the animationvar startButton = $("#startAnimation");var stopButton = $("#stopAnimation");stopButton.hide();//Toggle between playing the animation / pausing the animationstartButton.click(function() {$(this).hide();stopButton.show();am.run();});stopButton.click(function() {$(this).hide();startButton.show();am.stop();});  });
</script>  

Here's the working code, thanks to T.J. Crowder for fix + interesting blog post: Double-take

Solution: Changes in code are marked with //#########

var shapes = new Array();
shapes.push(new Shape(0,0,50,50,10));
shapes.push(new Shape(0,100,100,50,10));
shapes.push(new Shape(0,200,100,100,10));/***  AnimationManager class*  animate() runs the animation cycle*/
var AnimationManager = function(canvas)
{this.canvas = canvas;this.canvasWidth = canvas.width();this.canvasHeight = canvas.height();this.ctx = canvas.get(0).getContext('2d');this.running = true;var me = this; //#################################Added this in    this.start = function(){this.running = true;this.animate();}/** Allow the animations to run */this.run = function(){this.running = true;} /** Stop the animations from running */    this.stop = function(){this.running = false;}this.animate = function(){if(this.running){this.update();this.clear();this.draw();}//###################### Now using me.animate()setTimeout(function(){me.animate(); }, 40); //25 fps} /** Update all of the animations */this.update = function(){for(var i in shapes){shapes[i].moveRight();}}/** Clear the canvas */this.clear = function(){      this.ctx.clearRect(0,0, this.canvasWidth, this.canvasHeight);  }/** Draw all of the updated elements */this.draw = function(){       for(var i in shapes){this.ctx.fillRect(shapes[i].x, shapes[i].y, shapes[i].w, shapes[i].h);}}
}
Answer

The problem with the code is that in JavaScript, this is set (in the normal case) by how a function is called, not where it's defined. This is different than some other languages you might be used to such as Java or C#. So this line:

setTimeout(this.animate, 40);

...will indeed call your animate function, but with this set to the global object (window, on browsers). So all of those properties you're accessing (this.running, etc.) will not be looking at your object, but rather looking for those properties on window, which is clearly not what you want.

Instead, you can use a closure:

var me = this;
setTimeout(function() {me.animate();
}, 40);

That works because the anonymous function we're giving to setTimeout is a closure over the context in which it's defined, which includes the me variable we're setting up before defining it. By calling animate from a property on the object (me.animate()), we're telling JavaScript to set up this to be the object during the call.

Some frameworks have methods to create this closure for you (jQuery has jQuery.proxy, Prototype has Function#bind), and ECMAScript 5 (about 18 months old) defines a new Function#bind feature for JavaScript that does it. But you can't rely on it yet in browser-based implementations.

More discussion and solutions here: You must remember this


Possibly off-topic: In your code, you're using a lot of named function expressions. E.g.:

this.animate = function animate() { ... };

Named function expressions don't work correctly on IE prior to, I think, IE9. IE will actually create two completely separate functions (at two separate times). More here: Double-take


Update and a bit off-topic, but since all of your functions are defined as closures within your AnimateManager constructor anyway, there's no reason for anything you don't want to be public to be public, and you can completely get rid of issues managing this.

Here's the "solution" code from your updated question, making use of the closures you're already defining to avoid this entirely other than when defining the public functions. This also uses array literal notation for shapes and a normal for loop (not for..in) for looping through the array (read this for why: Myths and realities of for..in):

var shapes = [new Shape(0,0,50,50,10)),new Shape(0,100,100,50,10)),new Shape(0,200,100,100,10))
];/***  AnimationManager class*  animate() runs the animation cycle*/
var AnimationManager = function(canvas)
{var canvasWidth = canvas.width(),canvasHeight = canvas.height(),ctx = canvas.get(0).getContext('2d'),running = true, // Really true? Not false?me = this;// Set up our public functionsthis.start = AnimationManager_start;this.run   = AnimationManager_run;this.stop  = AnimationManager_stop;/** Start the animations **/function AnimationManager_start(){running = true;animate();}/** Allow the animations to run */function AnimationManager_run(){running = true;} /** Stop the animations from running */    function AnimationManager_stop(){running = false;}/** Internal implementation **/function animate(){if (running){update();clear();draw();}setTimeout(animate, 40); //25fps} /** Update all of the animations */function update(){var i;for (i = 0; i < shapes.length; ++i) // not for..in{shapes[i].moveRight();}}/** Clear the canvas */function clear(){      ctx.clearRect(0,0, canvasWidth, canvasHeight);  }/** Draw all of the updated elements */function draw(){       var i;for (i = 0; i < shapes.length; ++i) // not for..in{ctx.fillRect(shapes[i].x, shapes[i].y, shapes[i].w, shapes[i].h);}}
}

Each object created via new AnimationManager will get its own copy of the local variables within the constructor, which live on as long as any of the functions defined within the constructor is referenced anywhere. Thus the variables are truly private, and instance-specific. FWIW.

http://en.ppmy.cn/q/42182.html

Related Q&A

google maps - open marker infowindow given the coordinates

I am using google maps api v3.Using javascript, how can i open the infowindow of a marker given its coordinates?

How to show 5 rows instead of default 10 rows in datatable?

My datatable looks like this:Here it is showing default 10 datas in a single page.I need to show 1 to 5 of 58 entries so i tried to put max:5 but it is not working.I need to show only 5 data and the us…

how to get the value from dropdownlist in jQuery

i have a dropdownlist in my web-page how i can get the value from the selected option of themlike<select id="selme"> <option id="a" value="1">I need it</opt…

Mongoose Not Saving Data

I am having trouble with a simple query on my database. Following this tutorial: https://scotch.io/tutorials/build-a-restful-api-using-node-and-express-4 when Model.find() is called, he receives a JSON…

Check if object is a constructor - IsConstructor

I want to check if a JavaScript value is a constructor, that is, if it has a [[Construct]] internal method.ECMAScript defines IsConstructor, which does exactly this, but its an internal operation.So I …

Google site search catch search submit and trigger function

I have a page with just a searchbox on provided by google<gcse:search></gcse:search>Now when I type in the search box and hit enter I would like to trigger my script to run as the search re…

WebGL image rendering bad quality

I have a problem with image rendering in WebGL: I have a canva, with a quad that takes the whole canvas, and a texture which is supposed to be mapped onto the whole quad (I have set the correct texture…

Preloading Video Files - HTML5 playback

Im looking to preload a number of small video files on my website when it first loads. This is so they can be auto-played and used when a user clicks to enter a section of the website. I need the trans…

d3 v4 drag line chart with x and y axes

Im new to d3.js. I wanted to drag a line chart using its points. It is working fine without the x and y axes. I have used this example as reference: https://bl.ocks.org/mbostock/4342190With the axes to…

React Redux Reducer: this.props.tasks.map is not a function error

I am making a React Redux example; however, I ran into an issue and get the error below:TypeError: this.props.tasks.map is not a function[Learn More]I have tried many things and I cannot seem to unders…