I understand what is happening in the following snippet, and how to fix (with a binding or by making walk friend a method outside of the constructor), but why is this happening? It seems counter intuitive to me to have to bind a class' scope to its own methods.
class Person {
constructor(name, friend) {
this._name = name;
if(friend) {
this.walkFriend = friend.walk;
}
}
get name() {
return this._name.toUpperCase();
}
walk() {
console.log(this.name + ' is walking.');
}
}
let bob = new Person('Bob');
let bill = new Person('Bill', bob);
console.log(bob.name); // BOB
console.log(bill.name); // BILL
bill.walk() // Bill is walking.
bill.walkFriend(); // We expect 'BOB is walking', but we get 'BILL is walking.'
JavaScript has 3 types of scope: Block scope. Function scope. Global scope.
There are two types of Class in ES6: parent class/super class: The class extended to create new class are know as a parent class or super class. child/sub classes: The class are newly created are known as child or sub class. Sub class inherit all the properties from parent class except constructor.
'function' keyword is not allowed.
What are ES6 classes? In ES6, the "class" keyword and associated features are a new approach to creating prototype constructors. They are not true classes in a way that would be familiar to users of most other object-oriented languages.
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.)
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With