在JavaScript类函数中使用setTimeout() [英] Using setTimeout() within a JavaScript class function

查看:92
本文介绍了在JavaScript类函数中使用setTimeout()的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

目前动画方法调用正在运行一次,似乎setTimeout()没有完成它的工作。我已经设法让它工作,但是在一个非常hackish方法中,在类之外使用setTimeout。我想让动画循环成为AnimationManager类的工作。如果你能看到任何不好的做法,或者我出错了......请给我一个抬头!

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);
        }
    }
}

索引页面,它演示了我希望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>  

这是工作代码,感谢 TJ克劳德修复+有趣的博客文章: Double-take

Here's the working code, thanks to T.J. Crowder for fix + interesting blog post: 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中,这个是通过如何调用函数而不是在它定义的位置设置的(在正常情况下)。这与您可能习惯的其他语言(如Java或C#)不同。所以这一行:

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);

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

...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);

这是因为我们给 setTimeout 是对其定义的上下文的闭包,其中包括我们在定义它之前设置的 me 变量。通过从对象上的属性( me.animate())调用 animate ,我们告诉JavaScript设置up 这个作为调用期间的对象。

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.

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

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() { ... };

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

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

更新并且有点偏离主题,但因为所有功能都定义为封闭内您的 AnimateManager 构造函数无论如何,没有理由让您不想公开任何公开,并且您可以完全摆脱管理的问题这个

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.

这是您更新的问题中的解决方案代码,利用您已经定义的闭包来避免 this 完全不是在定义公共函数时。这也使用 shapes 的数组文字表示法和循环的正常(不是 for ..在中循环访问数组(请阅读以下内容: 的神话和现实...... ):

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 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);
        }
    }
}

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

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.

这篇关于在JavaScript类函数中使用setTimeout()的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆