[英]Make this keyword refer to instance rather than window object
In the code below I create an object called Slates and add some functions to it. 在下面的代码中,我创建了一个名为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);
};
The problem is that when I pass the function slates.render
as a callback into a function such as window.requestAnimationFrame
the this
keyword refers to the window
object rather than the Slates
instance, and therefore I get a 问题是,当我将函数
slates.render
作为回调传递给诸如window.requestAnimationFrame
之类的函数时, this
关键字引用的是window
对象而不是Slates
实例,因此我得到一个
Uncaught TypeError: Cannot read property 'clearRect' of undefined
未捕获的TypeError:无法读取未定义的属性'clearRect'
How do I fix this so that it refers to the instance of my object instead? 如何解决此问题,使其改为引用对象的实例? Here is a link to a codepen .
这是指向代码笔的链接。
You can use, for example, Function.prototype.bind
, which will return a function but with constant this value: 您可以使用例如
Function.prototype.bind
,该Function.prototype.bind
将返回一个函数,但此值保持不变:
window.requestAnimationFrame(slates.render.bind(slates))
or wrap this invocation into another function: 或将此调用包装到另一个函数中:
window.requestAnimationFrame(function() { slates.render() })
L-values and r-values L值和r值
In programming, there are two kinds of expressions: l-values ("l" for left) and r-values ("r" for right). 在编程中,有两种表达式:l值(左侧为“ l”)和r值(右侧为“ r”)。 An l-value is something that can be assigned to while an r-value is something that we can assign to something else.
l值是可以分配的东西,而r值是我们可以分配给其他东西的东西。 For example, say we had the following HTML
例如,假设我们有以下HTML
<div id="my-div" style="background-color: blue;"></div>
and the following JS: 和以下JS:
var myDiv = document.getElementById("my-div");
Then we could use myDiv.style.backgroundColor
as an l-value (ie assign something to it) 然后,我们可以将
myDiv.style.backgroundColor
用作myDiv.style.backgroundColor
值(即为其分配值)
myDiv.style.backgroundColor = "black"; // myDiv.style.backgroundColor is an l-value
or as an r-value (assign it / pass it to something else): 或作为r值(将其分配/传递给其他东西):
var newDiv = document.createElement("div");
newDiv.style.backgroundColor = myDiv.style.backgroundColor // now both are "black"
When we have a "dot chain" like in the above myDiv.style.backgroundColor
, its value in an expression is equal to the value of the last link in the chain. 当我们具有上述
myDiv.style.backgroundColor
的“点链”时,其在表达式中的值等于链中最后一个链接的值。 So when we use myDiv.style.backgroundColor
in an expression, we are simply using the backgroundColor
. 因此,当我们在表达式中使用
myDiv.style.backgroundColor
时,我们只是在使用backgroundColor
。 Assigning this to a new element, say newDiv
, does not create any sort of link between myDiv.style
and newDiv.style
. 将其分配给新元素(例如
newDiv
)不会在myDiv.style
和newDiv.style
之间创建任何形式的链接。
Likewise, when you assign slates.render
to window.onload
, you're assigning the render
function only - there is no longer any connection to slates
. 同样,当你将
slates.render
到window.onload
,你分配render
唯一的功能-不再有任何连接slates
。
You might wonder though... okay, but shouldn't this
be a link back to slates
? 你可能不知道,但...好吧,但不应该
this
是一个链接回slates
? Well, think about it, you never actually assigned anything to this
in your render
function, right? 好吧,考虑一下,您实际上从未在
render
函数中this
分配任何内容,对吧? You just used it. 您刚刚使用它。 The language engine supplies the value of
this
for you. 语言引擎提供的值,
this
给你。 Ordinary variables/properties in JS, eg slates
or slates.render
can be either l-values or r-values, depending on the context. 普通变量/属性在JS,例如
slates
或slates.render
可以是1-值或 R值,取决于上下文。 However, the this
keyword is only valid as an r-value
. 但是,
this
关键字仅作为r-value
有效。 If we try to assign directly to the this
keyword, we will see an error like this: 如果我们尝试直接分配给
this
关键字,我们将看到如下错误:
The error says what we already know -- that this
is not a valid l-value. 该错误说明了我们已经知道的-
this
不是有效的l值。 So where does it get its value from? 那么它从哪里获得价值呢?
This brings us to a discussion of scope in JS and why this
is different from other variables in an important way. 这使我们讨论了JS中的范围,以及为什么
this
在重要方面与其他变量不同。
Lexical scope (how variables work) 词汇范围(变量如何工作)
In JS, variables are lexically scoped , meaning that the value of a variable in a function call is determined not by the scope of the call, but rather by the scope of the function's definition. 在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
In this example, we define a function y
which itself returns a function which gets assigned to z
. 在此示例中,我们定义一个函数
y
,该函数本身返回一个分配给z
的函数。 When z
executes, it does console.log(x.name)
, but the question is... which x
should it refer to? 当
z
执行时,它会执行console.log(x.name)
,但问题是...它应该指向哪个x
? If JS were dynamically scoped, then we might expect it to print "outer", since this is the x
defined at the same "scope" as the variable z
. 如果JS是动态作用域的,那么我们可能希望它显示“外部”,因为这是在与变量
z
相同的“作用域”中定义的x
。 But it doesn't. 但事实并非如此。 It prints "inner" because JS is lexically scoped, meaning that
x
is equal to the x
which was in scope at the time of definition . 它打印“内部”,因为JS在词法范围内,这意味着
x
等于定义时在范围内的x
。
Dynamic Scope (how the this
keyword works) 动态范围(
this
关键字的工作原理)
If the this
keyword was lexically scoped like other variables in JS, the code in your post would have worked. 如果
this
关键字像JS中的其他变量一样在词法范围内,则您的帖子中的代码将起作用。 But it's not. 但事实并非如此。 While normal variables in JS are lexically scoped, the
this
keyword is not lexically scoped. 虽然JS中的普通变量在词法范围内,但
this
关键字在词法上不是范围。 It has dynamic scope. 它具有动态范围。 Meaning that its value depends not on the context in which your function is defined, but rather the context in which your function executes .
这意味着其值不取决于定义函数的上下文,而是取决于函数执行的上下文。 What context did you execute it in?
您在什么上下文中执行它? The window object!
窗口对象! If you imagine the load event being called like
window.onload()
then this becomes more clear. 如果您想象load事件像
window.onload()
一样被调用,那么这将变得更加清晰。
In fact, this is the behavior we want. 实际上,这是我们想要的行为。 Because if you had set a click event on a button, then you might do something like:
因为如果您在按钮上设置了click事件,则可能会执行以下操作:
var inputBox = document.getElementById("my-input-box");
function myFunc() {
alert("Uh oh, you typed " + this.value);
}
inputBox.onchange = myFunc;
This is the same behavior. 这是相同的行为。 What's a little confusing is the fact that
window
is also the default this
value. 令人困惑的是,
window
也是this
值的默认值。 (More on that below). (更多内容请见下文)。
With a few exceptions -- including the new
operator and the bind
, call
, and apply
methods -- the only way to pass a this
value into a function in JS is to call it on an object, like this: 除少数例外(包括
new
运算符和bind
, call
和apply
方法)外,将this
值传递给JS中的函数的唯一方法是在对象上对其进行调用,如下所示:
object.method(); // this now refers to object within `method`
An analogy 打个比方
Here's a real world analogy. 这是真实世界的类比。 Say that you have a ball.
假设您有一个球。 Define a method on the ball called
kick
. 在球上定义一个称为
kick
的方法。 The method is defined as move your leg to exert force on *it*
. 该方法定义为
move your leg to exert force on *it*
。 The "it" in that phrase is much like this
in JavaScript. 这个“它”在这句话很像是
this
在JavaScript中。 It's meaning is determined by context in which it is used, or in this case in which context the function is called . 它的含义取决于使用它的上下文,或者在这种情况下,该函数被调用的情况 。 Without the ball,
kick
still has meaning but the it
which it references is not defined. 无球,
kick
仍然具有意义,但it
它引用没有定义。 What I'm kicking is unknown. 我踢的是未知的。
When you pass a method to the window.onload handler like that, you're passing in the kick
function only, which on its own has no connection to the ball
. 当您将诸如此类的方法传递给window.onload处理程序时,您仅传递了
kick
函数,该函数本身与ball
没有任何关系。 Only when we call it like ball.kick()
does kick
get a this
value of ball
. 只有当我们像
ball.kick()
这样调用它时, kick
才会得到ball
的this
值。 In real life, there is no "default" it value. 在现实生活中,没有“默认” it值。 We don't assume that it refers to, say, the earth if we're not sure.
如果不确定,我们不会假设它是指地球。 Unlike real life however, JS does have a default
it
/ this
value. 但是,与现实生活不同,JS 确实具有默认的
it
/ this
值。 That default value is the window
object. 该默认值为
window
对象。 Trying to reason why the default is window
is probably futile - it's just an implementation decision that was made. 试图说明为什么默认为
window
可能是徒劳的-这只是做出的实现决策。
One solution 一种解决方案
In your case, the easiest thing to do is to use the bind
function. 在您的情况下,最简单的方法是使用
bind
函数。 The bind
function (introduced in the last version of JS) allows you to bind a certain this
value to a function, returning the new this
-bound function as its return value. bind
函数(在上一版JS中引入)使您可以将某个this
值绑定到函数,并返回新的this
-bound函数作为其返回值。 So for instance, if you had a function like 例如,如果您有一个类似
Ball.prototype.kick = function() {
this.touch();
this.move();
console.log(this);
}
and you did 而你做到了
myBall = new Ball();
kickBall = ball.kick.bind(myBall); // returns a new function, doesn't affect the original ball.kick
Then you could call kickBall
without the normal ball.kick()
syntax and its this
value would be myBall
. 然后,您可以
kickBall
不使用常规ball.kick()
语法的情况下调用kickBall
,并且其this
值为myBall
。
kickBall(); // myBall
window.setTimeout(myBall.kick, 1000); // window
A simpler solution 一个更简单的解决方案
The simpler (and more common) way is to just assign a function which itself calls slates.render()
to window.onload
, like so: 更简单(也是更常见)的方法是将一个本身调用
slates.render()
的函数分配给window.onload
,如下所示:
window.onload = function() {
slates.render(); // this works because doing obj.method() passes obj as a this value to method
};
Further reading 进一步阅读
Here's a better writeup of what this
means in JS: https://stackoverflow.com/a/3127440/2407870 这里是一个什么样的更好的书面记录
this
意味着JS: https://stackoverflow.com/a/3127440/2407870
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.