使此关键字引用实例而不是窗口对象

时间:2014-11-10 00:05:07

标签: javascript oop prototype

在下面的代码中,我创建了一个名为Slates的对象,并为其添加了一些函数。

"use strict";

function Slates() {
    this.canvas;
    this.ctx;
    this.width;
    this.height;
}

Slates.prototype.init = function() {
    this.canvas = document.getElementById("canvas");
    this.ctx = this.canvas.getContext("2d");
    this.resize();
};

Slates.prototype.resize = function() {
    this.width = window.innerWidth;
    this.height = window.innerHeight;
    this.canvas.width = this.width;
    this.canvas.height = this.height;
    console.log("resizing");
};

Slates.prototype.render = function() {
    this.ctx.clearRect(0, 0, this.width, this.height);
    this.ctx.fillStyle = "rgba(0,0,100,.5)";
    this.ctx.fillRect(0, 0, this.width, this.height);
};

window.onload = function() {
    var slates = new Slates();
    slates.init();
    window.onresize = slates.resize;
    window.requestAnimationFrame(slates.render);
};

问题是当我将函数slates.render作为回调函数传递给函数时 例如window.requestAnimationFrame this关键字引用window对象而不是Slates实例,因此我得到了

  

未捕获的TypeError:无法读取未定义的属性'clearRect'

如何解决此问题,以便它引用我的对象的实例?这是指向codepen的链接。

2 个答案:

答案 0 :(得分:5)

例如,您可以使用Function.prototype.bind,它将返回一个函数,但该值保持不变:

window.requestAnimationFrame(slates.render.bind(slates))

或将此调用包装到另一个函数中:

window.requestAnimationFrame(function() { slates.render() })

答案 1 :(得分:4)

L值和r值

在编程中,有两种表达式:l值(" l"左)和r值(" r"右)。 l值可以分配给,而r值是我们可以分配到其他东西的东西。例如,假设我们有以下HTML

<div id="my-div" style="background-color: blue;"></div>

以及以下JS:

var myDiv = document.getElementById("my-div");

然后我们可以使用myDiv.style.backgroundColor作为l值(即为其赋值)

myDiv.style.backgroundColor = "black"; // myDiv.style.backgroundColor is an l-value

或作为r值(将其指定/传递给其他人):

var newDiv = document.createElement("div");
newDiv.style.backgroundColor = myDiv.style.backgroundColor // now both are "black"

当我们有一个&#34;点链&#34;与上面的myDiv.style.backgroundColor一样,它在表达式中的值等于链中 last 链接的值。因此,当我们在表达式中使用myDiv.style.backgroundColor时,我们只是使用backgroundColor。将此分配给新元素(例如newDiv)不会在myDiv.stylenewDiv.style之间创建任何类型的链接。

同样,当您将slates.render分配给window.onload时,您只需分配render功能 - 不再与slates建立任何关联。

您可能想知道......好吧,但不应该this成为slates的链接吗?好吧,想想看,你从未在this函数中为render分配任何内容,对吗?你刚才用过它。语言引擎为您提供this的值。 JS中的普通变量/属性,例如slatesslates.render可以是l值 r值,具体取决于上下文。但是,this关键字仅作为r-value有效。如果我们尝试直接分配到this关键字,我们会看到如下错误:

enter image description here

错误说明了我们已经知道的事情 - this不是有效的l值。那么它从哪里获得价值?

这让我们讨论了JS的范围,以及为什么this与其他变量的重要区别。

词汇范围(变量如何工作)

在JS中,变量是词法范围,这意味着函数调用中变量的值不是由调用的范围决定的,而是由函数的范围决定的。定义。

var x = {name: "outer"};
function y() {
    var x = {name: "inner"};
    return function() {
        console.log(x.name);
    };
}

var z = y(); // z is a function here
z(); // logs "inner", not "outer" because JS is lexically scoped

在这个例子中,我们定义了一个函数y,它本身返回一个被赋值给z的函数。当z执行时,它会console.log(x.name),但问题是...... x应该引用哪个?如果JS是动态范围的,那么我们可能期望它打印&#34;外部&#34;,因为这是在同一&#34;范围&#34;中定义的x。作为变量z。但它并没有。它打印&#34;内部&#34;因为JS是词法范围的,这意味着x等于定义时范围内的x

动态范围(this关键字如何运作)

如果this关键字的词法范围与JS中的其他变量一样,那么您帖子中的代码就可以了。但事实并非如此。虽然JS中的常规变量是词法范围的,但this关键字不是词法范围。它具有动态范围。意味着它的值不取决于定义函数的上下文,而是取决于函数执行的上下文。你在什么上下文中执行它?窗口对象!如果您想象加载事件被调用为window.onload(),那么这将变得更加清晰。

事实上,这就是我们想要的行为。因为如果您在按钮上设置了单击事件,那么您可能会执行以下操作:

var inputBox = document.getElementById("my-input-box");
function myFunc() {
    alert("Uh oh, you typed " + this.value);
}
inputBox.onchange = myFunc;

这是相同的行为。有些令人困惑的是,window也是默认 this值。 (更多内容见下文)。

除了一些例外情况 - 包括new运算符和bindcallapply方法 - 传递{的唯一方法{1}} JS中的函数值是在对象上调用它,如下所示:

this

类比

这是一个真实世界的比喻。说你有一个球。在球上定义一个名为object.method(); // this now refers to object within `method` 的方法。该方法定义为kick。 &#34;它&#34;该短语与JavaScript中的move your leg to exert force on *it*非常相似。它的含义取决于使用它的上下文,或者在这种情况下,函数被称为。没有球,this仍有意义,但它引用的kick未定义。我踢的不明。

当你将一个方法传递给window.onload处理程序时,你只传递it函数,它本身与kick没有任何联系。只有当我们将其称为ball ball.kick()时才会kick获得thisball。在现实生活中,没有&#34;默认&#34;它的价值。如果我们不确定,我们不会认为它指的是地球。然而,与现实生活不同,JS 具有默认的it / this值。该默认值是window对象。试图说明默认值为window的原因可能是徒劳的 - 它只是一个实施决策。

一个解决方案

在您的情况下,最简单的方法是使用bind函数。 bind函数(在JS的最新版本中引入)允许您某个this值绑定到函数,返回新的this绑定函数作为其返回值。例如,如果你有一个像

这样的函数
Ball.prototype.kick = function() {

    this.touch();
    this.move();
    console.log(this);

}

你做了

myBall = new Ball();
kickBall = ball.kick.bind(myBall); // returns a new function, doesn't affect the original ball.kick

然后,您可以在没有正常kickBall语法的情况下拨打ball.kick(),其this值将为myBall

kickBall(); // myBall
window.setTimeout(myBall.kick, 1000); // window

更简单的解决方案

更简单(也更常见)的方法是只分配一个自己调用slates.render()window.onload的函数,如下所示:

window.onload = function() {

   slates.render(); // this works because doing obj.method() passes obj as a this value to method

};

进一步阅读

这里有一篇关于this在JS中的含义的更好的文章:https://stackoverflow.com/a/3127440/2407870