Good day
I recently ran into a strange situation where my this
value in on a member function of a class is undefined. I know there are a lot of questions relating undefined this
contexts, but I can't find any explanation of this issue. I would love to know why it happens. In this examples, why does the arrow function implementation of the setModified
function keep its this context, but the function on the class does not.
This example breaks in the setModified
function on the block class
class point {
constructor(x, y) {
this._x = x || 0;
this._changeEvents = [];
}
set changeEvent(eventFunction) {
this._changeEvents.push(eventFunction);
}
set x(value) {
this._x = value;
this.runChangeEvent();
}
set y(value) {
this._y = value;
this.runChangeEvent();
}
get x() {
return this._x;
}
get y() {
return this._y;
}
runChangeEvent() {
this._changeEvents.forEach(event => event(this));
}
}
class renderItem {
constructor(canvas) {
this._canvas = canvas;
}
render(){
}
}
class block extends renderItem {
constructor(canvas) {
super(canvas);
this._modified = true;
this._topLeft = new point(0, 0);
this._topLeft.changeEvent = this.setModified;
}
//Using a method on the class as a callback, it breaks
setModified(){
this._modified = true;//breaks, this is undefined
console.log(this);
}
//Sets
set topLeft(value) { this._topLeft = value; }
//Gets
get topLeft() { return this._topLeft }
}
//Creates an instance of the block
const bl = new block(null);
bl.topLeft.x = 20;
But when you change setModified
function to an arrow function (also on the class), it works:
class point {
constructor(x, y) {
this._x = x || 0;
this._y = y || 0;
this._changeEvents = [];
}
set changeEvent(eventFunction) {
this._changeEvents.push(eventFunction);
}
set x(value) {
this._x = value;
this.runChangeEvent();
}
set y(value) {
this._y = value;
this.runChangeEvent();
}
get x() {
return this._x;
}
get y() {
return this._y;
}
runChangeEvent() {
this._changeEvents.forEach(event => event(this));
}
}
class renderItem {
constructor(canvas) {
this._canvas = canvas;
}
render(){
}
}
class block extends renderItem {
constructor(canvas) {
super(canvas);
this._modified = true;
//Using an arrow function on the class instance as a callback, it works
this.setModified = () => {
this._modified = true;//works
console.log(this);
};
this._topLeft = new point(0, 0);
this._topLeft.changeEvent = this.setModified;
}
//Sets
set topLeft(value) { this._topLeft = value; }
//Gets
get topLeft() { return this._topLeft }
}
const bl = new block(null);
bl.topLeft.x = 20;
Why would the member function on the class lose the this context but not the arrow function?
An arrow function expression is a syntactically compact alternative to a regular function expression, although without its own bindings to the this, arguments, super, or new.target keywords.
The scope of an arrow function is no different than a regular function, but as you can see above from MDN , they handle the binding of this
different.
An arrow function does not create its own binding to this
, in fact it inherits from the enclosing scope (which is the class). In the class, the this._modified
exists.
An arrow function does not have its own this. The this value of the enclosing lexical scope is used; arrow functions follow the normal variable lookup rules.
this
is determined in the execution context, and as we've shown the arrow function works because this
stays with the this
of the enclosing scope. However with a normal function...
In strict mode, however, if the value of this is not set when entering an execution context, it remains as undefined
If you console.log(this)
before the error in your function you will see it is undefined
. But why is it undefined
? Read here under the implicitly lost section. But basically default binding is applied when calling it as a plain, undecorated function reference, which it is from the assignment. And under strict mode, this
will return undefined
while not in the global scope.
Implicit binding doesn't occur in your case and this
can be implicitly lost.
One of the most common frustrations that this binding creates is when an implicitly bound function loses that binding, which usually means it falls back to the default binding, of either the global object or undefined, depending on strict mode.
If you change your call in runChangeEvent()
to event.call(this)
you will see that this
is actually the point
's this (not what you want). If you call this._topLeft.changeEvent = this.setModified.bind(this);
you will have the scope you want.
Calling f.bind(someObject) creates a new function with the same body and scope as f, but where this occurs in the original function, in the new function it is permanently bound to the first argument of bind, regardless of how the function is being used.
Check out that link for more information and also I recommend this online book referenced earlier.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.