【问题标题】:Using setTimeout() within a JavaScript class function在 JavaScript 类函数中使用 setTimeout()
【发布时间】:2011-10-23 07:10:05
【问题描述】:

是否可以在 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);
        }
    }
}

【问题讨论】:

  • 您是否正在寻找setInterval 函数?
  • 顺便说一句,稍微偏离主题,但有一种更简单的方法来编写它。我已经用一个例子更新了我的答案。最好的,

标签: javascript oop settimeout javascript-objects


【解决方案1】:

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

setTimeout(this.animate, 40);

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

相反,您可以使用闭包:

var me = this;
setTimeout(function() {
    me.animate();
}, 40);

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

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

更多讨论和解决方案在这里:You must remember this


可能跑题了:在您的代码中,您使用了很多命名函数表达式。例如:

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

命名函数表达式在 IE 之前不能在 IE 上正常工作,我认为是 IE9。 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。

【讨论】:

  • 我只想说(尽管这篇文章现在已经“旧”了),这是一个很好的答案——这里有很多很棒的细节。
猜你喜欢
  • 2018-09-22
  • 2017-11-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多