【问题标题】:requestAnimationFrame with this keyword带有此关键字的 requestAnimationFrame
【发布时间】:2023-12-21 00:38:01
【问题描述】:

我正在使用webkitRequestAnimationFrame,但在对象内部使用它时遇到问题。如果我传递了 this 关键字,它将使用 window 并且我找不到它使用指定对象的方法。

例子:

Display.prototype.draw = function(){
  this.cxt.clearRect(0, 0, this.canvas.width, this.canvas.height);
  //Animation stuff here.

  window.webkitRequestAnimationFrame(this.draw);
};

我也试过了,但没用:

Display.prototype.draw = function(){
  this.cxt.clearRect(0, 0, this.canvas.width, this.canvas.height);
  //Animation stuff here.

  var draw = this.draw;
  window.webkitRequestAnimationFrame(draw);
};

【问题讨论】:

    标签: javascript object animation this


    【解决方案1】:

    我正在尝试传递 webkitRequestAnimationFram 所在的函数 display.draw。

    webkitRequestAnimationFrame 大概会调用你传入的函数,如下所示:

    function webkitRequestAnimationFrame(callback)
    {
        // stuff...
        callback();
        // other stuff...
    }
    

    此时,您已将draw 函数与其调用上下文分离(分离)。您需要将函数 (draw) 绑定到其上下文(Display 的实例)。

    你可以使用Function.bind,但是这个requires JavaScript 1.8 support(或者只使用推荐的补丁)。

    Display.prototype.draw = function()
    {
        // snip...
    
        window.webkitRequestAnimationFrame(this.draw.bind(this));
    };
    

    【讨论】:

    • 我正在尝试传递 display.draw,这是 webkitRequestAnimationFram 所在的函数。
    • 哦,我想我看到了问题:你可以传递函数,但是webkitRequestAnimationFrame 稍后会调用它,this 不会指向正确的对象,因为你已经“分离”了来自其对象的函数。查看我的编辑(待定)。
    • bind 方法工作得很好(从不知道,thnx :])但是通过闭包传递this.draw(); 仍然会引发错误。 Uncaught TypeError: Object [object DOMWindow] has no method 'draw'
    • 嗯,还是不行Uncaught TypeError: Object #<Display> has no method 'fn'不过不用担心,bind方法已经足够好了。感谢您的帮助。
    • 我不知道 Function.bind 方法。我想象中的帽子送给你! :)
    【解决方案2】:

    这个怎么样:

    Display.prototype.draw = function(){
      this.cxt.clearRect(0, 0, this.canvas.width, this.canvas.height);
      //Animation stuff here.
    
      window.webkitRequestAnimationFrame( $.proxy(function() {this.draw()}, this) );
    };
    

    ...假设您使用 jquery

    【讨论】:

    • 如何在没有jquery的情况下使用它??我尝试使用绑定,但它给出的最大堆栈大小超出了错误。
    【解决方案3】:

    您不必使用“this”。 保持简单。

    var game = {
          canvas:null,
          context:null,
    
        init:function(){
                // init canvas, context, etc
        },      
    
        update:function(){
            //do something
            game.render();                        
            requestAnimationFrame(game.update, game.canvas);        
        },            
    };
    

    【讨论】:

    • 不错。我需要一个可以从类外部访问的方法,所以我使用了这个成语:var update = this.update = function() { render(); requestAnimationFrame(update); }
    【解决方案4】:

    您可能还想使用 requestAnimationFrame shim 使其适用于所有浏览器 https://github.com/kof/animation-frame

    【讨论】:

      【解决方案5】:

      我不能保证这是一个好主意并且我是对的,但是在每个 requestAnimationFrame 上运行 .bind 意味着在每次迭代中创建一个新函数。对我来说听起来不太对劲。

      这就是为什么在我的项目中我缓存了绑定函数以避免反模式。

      简单示例:

      var Game = function () {
          this.counter = 0;
          this.loop = function () {
              console.log(this.counter++); 
              requestAnimationFrame(this.loop);
          }.bind(this);
          this.loop();
      }
      var gameOne = new Game();
      

      如果您有一个具有原型继承的更复杂的项目,您仍然可以创建一个缓存函数,并将“this”绑定在对象的构造函数中

      var Game = function () {
          this.counter = 0;
          this.loopBound = this.loop.bind(this);
          this.loopBound();
      }
      Game.prototype.loop = function () {
          console.log(this.counter++); 
          requestAnimationFrame(this.loopBound);
      }
      var gameOne = new Game();
      

      想法? http://jsfiddle.net/3t9pboe8/(查看控制台)

      【讨论】:

      • 我修改了您的fiddle 以(我认为)测试缓存绑定函数与不缓存之间的区别。我很惊讶地看到两者之间的性能几乎相同(在 Firefox 47、Chrome 52 和 IE 11 中)。看起来好像bind 创建的函数被浏览器缓存了,但我不知道实际发生了什么。我什至尝试单独运行每个程序以避免浏览器优化requestAnimationFrame 的干扰。
      • @SeanH 在这种情况下,“循环”是一个非常简单的函数,由一个计数器递增和一个条件检查组成。我想知道在更复杂的函数的每次迭代中使用“绑定”是否会产生更大的差异。在从不超过 60fps 的 requestAnimationFrame 上进行这种做法的好处可能是微不足道的。
      【解决方案6】:

      现在 ES6/2015 已经发布,如果您使用的是转译器,那么箭头函数将具有词法 this 绑定,因此可以代替:

      window.webkitRequestAnimationFrame(this.draw.bind(this));
      

      你可以这样做:

      window.webkitRequestAnimationFrame(() => this.draw());
      

      这有点干净。

      我已经有效地将 Typescript 转换为 ES5。

      【讨论】:

      • 这是我现在使用的。
      • 我刚刚使用了这种技术,我不得不在箭头函数中放置一个参数,对应于 rAF() 回调接收的 timeStamp 参数:(t) => this.draw(t)
      【解决方案7】:

      除了bind 方法和箭头函数解决方案(由 Jamaes World 的回答提供)之外,另一个(相当老的)解决方法可能是:

      var self = this
      window.webkitRequestAnimationFrame(
          function() {
              self.draw()
          }
      );
      

      【讨论】:

        最近更新 更多