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