Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scope within ES6 Classes

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.'
like image 202
Brendan Quinn Avatar asked Jul 27 '16 12:07

Brendan Quinn


People also ask

How many type of scope does JavaScript in ES6?

JavaScript has 3 types of scope: Block scope. Function scope. Global scope.

What are classes in ES6?

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.

Which keyword is not allowed in ES6 class definition?

'function' keyword is not allowed.

Do classes exist in JavaScript 6 ES6?

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.


2 Answers

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.)

like image 40
Redu Avatar answered Oct 14 '22 20:10

Redu


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).

like image 120
T.J. Crowder Avatar answered Oct 14 '22 19:10

T.J. Crowder