简体   繁体   English

在JavaScript类函数中使用setTimeout()

[英]Using setTimeout() within a JavaScript class function

Is it possible to use setTimout() within a JavaScript object? 是否可以在JavaScript对象中使用setTimout()?

Currently the animation method call is running once, it seems that the setTimeout() isn't doing its job. 目前动画方法调用运行一次,似乎setTimeout()没有完成它的工作。 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. 我已经设法让它工作,但是在一个非常hackish方法中,在类之外使用setTimeout。 I'd like to make the animation loop a job for the AnimationManager class. 我想让动画循环成为AnimationManager类的工作。 If you can see any bad practice, or where i'm going wrong.. please give me a heads up! 如果你能看到任何不良做法,或者我出错了......请给我一个抬头!

JavaScript: 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: 索引页面中的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>  

Here's the working code, thanks to TJ Crowder for fix + interesting blog post: Double-take 这是工作代码,感谢TJ Crowder修复+有趣的博客文章: 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);
        }
    }
}

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. 代码的问题在于,在JavaScript中, this是通过如何调用函数而不是在其定义的位置设置(在正常情况下)。 This is different than some other languages you might be used to such as Java or C#. 这与您可能习惯的其他语言(如Java或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). ...确实会调用你的animate函数,但是this设置为全局对象( window ,浏览器)。 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. 因此,您正在访问的所有这些属性( this.running等)都不会查看您的对象,而是在window上查找这些属性,这显然不是您想要的。

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. 这是有效的,因为我们给setTimeout的匿名函数是对它定义的上下文的闭包,其中包括我们在定义它之前设置的me变量。 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. 通过调用animate从对象上的属性( me.animate()我们告诉JavaScript来建立this是在通话过程中的对象。

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. 有些框架有为你创建这个闭包的方法(jQuery有jQuery.proxy ,Prototype有Function#bind ),而ECMAScript 5(大约18个月)为JavaScript定义了一个新的Function#bind功能。 But you can't rely on it yet in browser-based implementations. 但是在基于浏览器的实现中你不能依赖它。

More discussion and solutions here: You must remember this 这里有更多的讨论和解决方案: 你必须记住this


Possibly off-topic: In your code, you're using a lot of named function expressions . 可能偏离主题:在您的代码中,您使用了许多命名函数表达式 Eg: 例如:

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

Named function expressions don't work correctly on IE prior to, I think, IE9. 在我认为IE9之前,命名函数表达式在IE上无法正常工作。 IE will actually create two completely separate functions (at two separate times). IE实际上会创建两个完全独立的函数(在两个不同的时间)。 More here: Double-take 更多信息: 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 . 更新和有点偏离主题,但由于你的所有函数都被定义为AnimateManager构造函数中的闭包,所以没有理由让你不想公开任何公开,并且你可以完全摆脱管理问题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定义的公共职能时不是完全等。 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 ): 这也使用数组文字符号表示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);
        }
    }
}

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. 通过new AnimationManager创建的每个对象将在构造函数中获得自己的局部变量副本,只要构造函数中定义的任何函数在任何地方被引用,它们就会存在。 Thus the variables are truly private, and instance-specific. 因此,变量是真正的私有,并且是特定于实例的。 FWIW. FWIW。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM