8

In a current JavaScript project where ES6 class syntax and get/set syntax are used I stumbled upon a behaviour I cannot explain.

First, an extracted demo that works as expected:

class A {
    constructor() {
        this.__value = null;
    }

    get value() {
        return this.__value;
    }

    set value(value) {
        this.__value = value;
    }
}

class B extends A { }

let b = new B();
b.value = 2;
console.log(b.value); // output: 2

Setting and getting b.value (defined in A.prototype) works.

Now consider the following demo in which I moved just the setter from A to B:

class A {
    constructor() {
        this.__value = null;
    }

    get value() {
        return this.__value;
    }
}

class B extends A {
    set value(value) {
        this.__value = value;
    }
}

let b = new B();
b.value = 2;          // b.__value is 2
console.log(b.value); // output: undefined

Setting value works (since b.__value is indeed 2), but the getter does not seem to exist although it is still defined in A.prototype.

What is the catch here?

cbettinger
  • 83
  • 4
  • In this explicit scenario that comment is not helpful at all. – cbettinger Mar 14 '19 at 12:25
  • Sorry not to have helped you at all, you can go on saying incorrect things like "B's prototype A" in your questions if you like, just tried to make you understand classes and prototypes are really not the same thing – Kaddath Mar 14 '19 at 12:35
  • That was unprecise, thank you. I fixed this. – cbettinger Mar 14 '19 at 13:11

1 Answers1

4

When you try to retrieve a property, and the property isn't on the instance, the engine will look up the prototype chain for the first object in the chain which has a property descriptor for the property in question. When said descriptor is found, if it has a getter, that getter is invoked. Otherwise, if there is no getter, it will retrieve the plain value for that property, if there is one.

In the second case, the property descriptor is on B.prototype. But B.prototype does not have a getter for value (nor does B.prototype have a plain value for value)! So, undefined is returned.

If B.prototype had a getter for value, it would be invoked:

'use strict';

class A {
    constructor() {
        this.__value = null;
    }

    get value() {
        return this.__value;
    }
}

class B extends A {
    set value(value) {
        this.__value = value;
    }
    get value() {
        console.log('trying to get value');
    }
}

let b = new B();
b.value = 2;
b.value;

But it doesn't have one. The engine doesn't keep looking for a getter upwards in the prototype chain - instead, it'll just stop and return undefined, because no getter (or plain value) was found on the first object in the prototype chain which hasOwnProperty('value').

If you have a getter, and you want to be able to set that same property, the setter must be on the same object as the getter, and vice-versa.

CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • So the problem here is, that the lookup in prototype chain for a property descriptor does not differentiate between a get and a set. Even though I try to **get** b.value, the lookups stops at B.prototype (because there is a **set** property descriptor) - but the expected **get** property descriptor is not there. I understand. Weird JS :) – cbettinger Mar 14 '19 at 09:37
  • @cbettinger it can be called weird, but you have to be aware that ES6 class syntax is just an emulation of what are classes in most other languages. I sometimes prefer to work with prototypes directly, it's really powerful when you understand it. – Kaddath Mar 14 '19 at 09:53