了解 .bind() 与参数的关系

Understanding .bind() in relation to arguments

我正在审查一些游戏代码,但我有一段不太明白。

var game = (function(){

   // Start with our constructor
   function Game(){
        this.viewport = document.getElementById('viewport');
        this.ctx = this.viewport.getContext('2d');
    };

    Game.prototype.updateAnimation = function(t){

        // work out the delta time
        this.dt = this.lastFrameTime ? ((t - this.lastFrameTime)/1000.0).fixed() : 0.016;

        // store last frame time
        this.lastFrameTime = t;

        // run relevent updates here

        // queue the next animation time.
        this.animationId = window.requestAnimationFrame( this.updateAnimation.bind(this), this.viewport );
    }

    // return game class
    return Game;

})();

然后

// call new game object
var clientGame = new game();

// call event loop
clientGame.updateAnimation(new Date().getTime());

当请求 window.requestAnimationFrame( this.updateAnimation.bind(this), this.viewport ); 运行时。它如何知道参数 t 的值。好像每次都会更新,但我不明白为什么。有人可以解释发生了什么事吗?谢谢。

编辑

Number 原型链上附加了一个名为 fixed() 的函数。这是为了清楚起见

 Number.prototype.fixed = function(n) { n = n || 3; return parseFloat(this.toFixed(n)); };

首先代码可以写的更好,不需要每次都重新绑定函数,一次就够了(比如在对象初始化的时候)

其次,函数requestAnimationFrame只接受一个参数,所以第二个参数没有意义,第一个参数是浏览器发送给回调的t

方法 .bind 不会更改函数参数(可能 arguments.callee 除外,尽管这是多余的)但仅更改函数的 context 因此第一个参数保持 t (timestamp)

更多信息https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame

编辑

所以用户正在使用一些允许向 requestAnimationFrame 发送两个参数的 polyfill

所以requestAnimationFrame有一个参数,一个回调函数,它获取时间戳作为参数。 绑定函数 returns 原始函数但在不同的范围内。因此 requestAnimationFrame 使用时间戳调用 updateAnimation,这是您的时间来源。

现在要了解 bind 调用背后的原因,您需要了解 JavaScript 中的范围。

当您定义一个函数并将其作为参数传递给另一个函数时,您实际上是在第二个函数的范围内调用第一个函数。这意味着如果你有一个 class 实例并且你想将一个实例方法作为回调传递给一个函数,你要么需要使用一个箭头函数来保留当前范围,要么你将方法绑定到正确的范围(你的 class实例)。

我希望下面的例子能更好地说明我在说什么。

class OuterScope {
    constructor() {
        this.label = 'test';
    }

    print() {
        console.log(this.label);
    }
}

function innerScope(callback) {
    callback();
}

var scope = new OuterScope();
innerScope(scope.print); // throws error because the scope the function is called in doesn't have a property called label
innerScope(scope.print.bind(scope)); // prints label

现在将此应用于您的 requestAnimationFrame 问题:

class Game {
    updateAnimation(t) {
        // this.lastFrameTime would always be undefined when called without binding
        this.dt = this.lastFrameTime ? ((t - this.lastFrameTime)/1000.0).fixed() : 0.016;

        // store last frame time
        this.lastFrameTime = t;

        window.requestAnimationFrame(this.updateAnimation.bind(this)); // updateAnimation now always runs in the scope of the current Game instance
        // window.requestAnimationFrame(this.updateAnimation); // updateAnimation runs in the scope of the window
    }
}

window.requestAnimationFrame = function requestAnimationFrame(callback) {
    callback(new Date().getTime()); // calls the callback with one argument
}