简体   繁体   English

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

[英]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.stylenewDiv.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.renderwindow.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,例如slatesslates.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运算符和bindcallapply方法)外,将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才会得到ballthis值。 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.

相关问题 引用类/实例而不是 object 文字属性的正确方法? - Correct way to refer to class/instance rather than object literal property? 如果从嵌套对象的方法中使用,如何使“ this”关键字引用当前实例? - How to make “this” keyword refer to current instance, if used from a nested object's method? 使&#39;this&#39;关键字引用调用对象(javascript) - make the 'this' keyword refer to the calling object (javascript) Javascript&#39;this&#39;关键字返回href属性,而不是锚标记上的Object - Javascript 'this' keyword returning href attribute rather than Object on anchor tag 滚动HTML元素而不是整个window对象的内容 - Scrolling the content of an HTML element, rather than the whole window object 为什么document.body的事件处理程序中的'this'关键字引用全局window object? - Why does the 'this' keyword inside event handlers of document.body refer to the global window object? 如何使Transcrypt编译为对象而不是dict? - How can one make Transcrypt compile to object rather than dict? 将小部件对象状态存储到ipython内核对象实例而不是对象类 - Store widget object state to an ipython kernel object instance rather than the object class 构造函数中的“this”关键字是否指的是类实例的名称? - Does the “this” keyword inside a constructor refer to the name of the instance of the class? 检查原型的继承,而不是实例 - Check inheritance of prototype, rather than instance
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM