Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getter / Setter and Prototype Chain [duplicate]

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?

like image 927
cbettinger Avatar asked Mar 14 '19 08:03

cbettinger


1 Answers

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.

like image 162
CertainPerformance Avatar answered Sep 17 '22 08:09

CertainPerformance