在JavaScript类函数中使用setTimeout()

时间:2011-08-09 14:36:54

标签: javascript oop settimeout javascript-objects

是否可以在JavaScript对象中使用setTimout()?

目前动画方法调用正在运行一次,似乎setTimeout()没有完成其工作。我已经设法让它工作,但是在一个非常hackish方法中,在类之外使用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);
        }
    }
}

1 个答案:

答案 0 :(得分:15)

代码的问题在于,在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


可能偏离主题:在您的代码中,您使用了很多命名的函数表达式。 E.g:

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

在我认为IE9之前,命名函数表达式在IE上无法正常工作。 IE实际上会创建两个完全独立的函数(在两个不同的时间)。更多信息:Double-take


更新并且有点偏离主题,但是因为你的所有函数都被定义为AnimateManager构造函数中的闭包,所以你没有理由不想做任何事情。公众公开,你可以完全摆脱管理this的问题。

这是来自您更新的问题的“解决方案”代码,利用您已经定义的闭包来避免this完全不同于定义公共函数时。这也使用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创建的每个对象都将获得构造函数中局部变量的自己的副本,只要构造函数中定义的任何函数在任何地方被引用,它们就会存在。因此,变量是真正的私有,并且是特定于实例的。 FWIW。