javascript - 在 JavaScript 类函数中使用 setTimeout()

标签 javascript oop settimeout javascript-objects

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

最佳答案

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


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

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

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


更新 有点跑题了,但是由于您的所有函数都在 AnimateManager 构造函数中定义为闭包,所以您没有理由不这样做想要公开就公开,您可以完全摆脱管理 this 的问题。

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

关于javascript - 在 JavaScript 类函数中使用 setTimeout(),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6997921/

相关文章:

javascript - jQuery:如何获取点击事件以触发具有相同 ID 的所有元素?

javascript - 与谷歌和他的 API 或登录按钮有困难

javascript - 为什么 setTimeout 不适用于 React Redux 中的异步操作创建者?

JavaScript setTimeout 不起作用

php - 是否有类似于 setTimeout() (JavaScript) 的函数用于 PHP?

javascript - CckEditor 复制/粘贴实现 - 从编辑器复制和从外部编辑器复制

javascript - 将 json 数组格式化为图表输入

java - 依赖倒置原理的内在

c# - 从具有抽象属性的泛型类继承

c++ - 不完整类型循环依赖c++的无效使用