Scope within ES6 Classes
What's happening is that there is no intrinsic connection between a "method" in an ES2015 ("ES6") class and the instance, just like there isn't with the older style constructor functions.¹ friend.walk
just returns a raw method reference, there's nothing about it that binds it to friend
unless you do so yourself. Putting it another way, friend.walk === Person.prototype.walk
is true
. E.g., your counter-intuitive understanding is correct (except it's not about scope, but rather the value of this
). :-)
Remember that the new class
stuff is almost entirely just syntactic sugar (but, you know, the good kind of sugar). Your Person
class almost exactly equates to this ES5 code:
var Person = function Person(name, friend) {
this._name = name;
if(friend) {
this.walkFriend = friend.walk;
}
};
Object.defineProperty(Person.prototype, "name", {
get: function() {
return this._name.toUpperCase();
},
configurable: true
});
Object.defineProperty(Person.prototype, "walk", {
value: function() {
console.log(this.name + ' is walking.');
},
writable: true,
configurable: true
});
You've said you know how to solve it, and indeed both of your solutions will work, either binding:
constructor(name, friend) {
this._name = name;
if(friend) {
this.walkFriend = friend.walk.bind(frield); // **
}
}
or creating walk
within the constructor as an arrow function, instead of on the prototype:
constructor(name, friend) {
this._name = name;
this.walk = () => { // **
console.log(this.name + ' is walking.'); // **
}; // **
if(friend) {
this.walkFriend = friend.walk;
}
}
¹ There is an intrinsic connection between the method and the prototype of the class it's defined within, which is used if you use the super
keyword within the method. The spec calls that link the [[HomeObject]] field of the method (but you can't access it in code, and it can be optimized away by the JavaScript engine if you don't use super
in the method).
There are only 4 use cases of the this
in JS. You might like to check this for a good read. In this particular case you have an instruction this.walkFriend = friend.walk;
which refers to the walk
function in the object passed in by the friend
argument. It's neither a new function definition nor belongs to the object it resides in. It's just a referral to the function existing in the object referred as friend
. However when you invoke it the this
in the friend.walk
function becomes the object from where it's been invoked. Hence you get the name
property of the object referred by the this
.
There are several ways to fix this. One as you would guess is bind. You can make it like this.walkFriend = friend.walk.bind(friend);
or another is, to invoke it from within it's own scope like this.walkFriend = _ => friend.walk();
(I presume since you use classes arrows should also be fine with you.)