在JavaScript类函数中使用setTimeout()

Jac*_*ack 4 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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

索引页面中的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>  
Run Code Online (Sandbox Code Playgroud)

这是工作代码,感谢TJ 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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

T.J*_*der 15

代码的问题在于,在JavaScript中,this通过如何调用函数(而不是在其定义的位置)设置(在正常情况下).这与您可能习惯的其他语言(如Java或C#)不同.所以这一行:

setTimeout(this.animate, 40);
Run Code Online (Sandbox Code Playgroud)

...确实会调用你的animate函数,但this设置为全局对象(window在浏览器上).因此,您正在访问的所有这些属性(this.running等等)都不会查看您的对象,而是查找这些属性window,这显然不是您想要的.

相反,你可以使用一个闭包:

var me = this;
setTimeout(function() {
    me.animate();
}, 40);
Run Code Online (Sandbox Code Playgroud)

这是有效的,因为我们给出的匿名函数是setTimeout对其定义的上下文的闭包,其中包括me我们在定义它之前设置的变量.通过animate从object(me.animate())上的属性调用,我们告诉JavaScript this在调用期间设置为对象.

有些框架有为你创建这个闭包的方法(jQuery有jQuery.proxy,Prototype有Function#bind),而ECMAScript 5(大约18个月)Function#bind为JavaScript 定义了一个新功能.但是在基于浏览器的实现中你不能依赖它.

这里有更多的讨论和解决方案:你必须记住this


可能偏离主题:在您的代码中,您使用了许多命名函数表达式.例如:

this.animate = function animate() { ... };
Run Code Online (Sandbox Code Playgroud)

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


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

这是来自更新问题的"解决方案"代码,利用您已经定义的闭包来避免this完全除了定义公共函数之外的其他问题.这也使用数组文字符号shapes和正常for循环(不for..in)来循环遍历数组(阅读这个为什么:神话和现实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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

通过创建的每个对象new AnimationManager将在构造函数中获得自己的局部变量副本,只要构造函数中定义的任何函数在任何地方被引用,它们就会存在.因此,变量是真正的私有,并且是特定于实例的.FWIW.