是否可以在 JavaScript 对象中使用 setTimout()?
目前动画方法调用正在运行一次,似乎 setTimeout() 没有完成它的工作。我已经设法让它工作了,但是在一个非常骇人听闻的方法中,在使用 setTimeout 的类之外有一个函数。我想让动画循环成为 AnimationManager 类的一项工作。如果您看到任何不好的做法,或者我哪里出错了..请提醒我!
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,它演示了我希望 AnimationManager 如何工作:
<script type="text/javascript">
$(document).ready(function() {
var canvas = $('#myCanvas');
var am = new AnimationManager(canvas);
am.start();
//If true play the animation
var startButton = $("#startAnimation");
var stopButton = $("#stopAnimation");
stopButton.hide();
//Toggle between playing the animation / pausing the animation
startButton.click(function()
{
$(this).hide();
stopButton.show();
am.run();
});
stopButton.click(function()
{
$(this).hide();
startButton.show();
am.stop();
});
});
</script>
这是工作代码,感谢 T.J. Crowder修复 + 有趣的博客文章:Double-take
解决方案:代码中的更改标记为//#########
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);
}
}
}
最佳答案
代码的问题在于,在 JavaScript 中,this
由函数的调用方式设置(在正常情况下),而不是定义函数的位置。这与您可能习惯的其他一些语言(例如 Java 或 C#)不同。所以这一行:
setTimeout(this.animate, 40);
...确实会调用您的animate
函数,但this
设置为全局对象(window
,在浏览器上)。因此,您正在访问的所有这些属性(this.running
等)不会查看您的对象,而是在 window
上查找这些属性,即显然不是你想要的。
相反,您可以使用闭包:
var me = this;
setTimeout(function() {
me.animate();
}, 40);
之所以可行,是因为我们给 setTimeout
的匿名函数是一个闭包,覆盖了定义它的上下文,其中包括我们正在设置的 me
变量在定义它之前。通过从对象的属性调用 animate
(me.animate()
),我们告诉 JavaScript 将 this
设置为通话期间的对象。
一些框架有为你创建这个闭包的方法(jQuery 有 jQuery.proxy
,Prototype 有 Function#bind
),ECMAScript 5(大约 18 个月大)定义了一个新的 Function#bind
。 JavaScript 的功能。但您还不能在基于浏览器的实现中依赖它。
此处有更多讨论和解决方案:You must remember this
可能偏离主题:在您的代码中,您使用了很多命名函数表达式。例如:
this.animate = function animate() { ... };
我认为,在 IE9 之前,命名函数表达式在 IE 上不能正常工作。 IE 实际上会创建两个完全独立的函数(在两个不同的时间)。更多信息:Double-take
更新 有点跑题了,但是由于您的所有函数都在 AnimateManager
构造函数中定义为闭包,所以您没有理由不这样做想要公开就公开,您可以完全摆脱管理 this
的问题。
这是您更新后的问题的“解决方案”代码,利用您已经定义的闭包来完全避免 this
,而不是在定义公共(public)函数时。这也为 shapes
和普通的 for
循环(不是 for..in
)使用数组文字符号来遍历数组(阅读这个为什么: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 functions
this.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);
}
}
}
通过 new AnimationManager
创建的每个对象都将在构造函数中获得其自己的 局部变量副本,只要构造函数中定义的任何函数都存在在任何地方都被引用。因此,变量是真正私有(private)的,并且是特定于实例的。 FWIW。
关于javascript - 在 JavaScript 类函数中使用 setTimeout(),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6997921/