skip to Main Content

Foreword

Going through MDN Docs, I found 2 interesting points about object property inheritance in JS.
The article on Object.defineProperty() points out that

Unlike accessor properties, data properties are always set on the object itself, not on a prototype. However, if a non-writable data property is inherited, it is still prevented from being modified on the object.

To my understanding, it means that any accessor property or non-writable data property of an object cannot be overridden by it’s child object unless such a property is defined on the child object explicitly using Object.defineProperty() static method.

Also, there’s this peculiar aspect of super which sets a property on the current this object as stated as follows:

Setting properties of super, such as super.x = 1, behaves like Reflect.set(Object.getPrototypeOf(objectLiteral), "x", 1, this). This is one of the cases where understanding super as simply "reference of the prototype object" falls short, because it actually sets the property on this instead.

But using super also doesn’t allow overriding of accessor or non-writable data property as highlighted below:

However, super.x = 1 still consults the property descriptor of the prototype object, which means you cannot rewrite non-writable properties, and setters will be invoked.


The Question

Consider the following piece of code:

class A {
    get ap() { return this.x;}
    set ap(arg) { this.x = arg; }
}
Object.defineProperty(A.prototype, 'dp', {writable: true, value: 'writable'});
Object.defineProperty(A.prototype, 'nwdp', {writable: false, value: 'locked'});

class B extends A {
    method() {
        super.dp = 200; // Override it
        super.ap = 'bird';  // Doesn't override it; Invokes setter, ap at A.prototype
        super.nwdp = 'unlocked';  // Doesn't override it; Throws TypeError
    }
}

b = new B()

When called b.method(), it sets dp and x on the instance, b (checked using Object.getOwnPropertyNames(b)) but will throw TypeError.

Uncaught TypeError: Cannot assign to read only property 'nwdp' of object '#B'

Similarly, when super.prop = <any_value> is used in an object literal, it functions the same way but it doesn’t throw any error.

let obj1 = {
    dp: 'writable',

    get ap() { return this.x; },
    set ap(arg) { this.x = arg; }
};
Object.defineProperty(obj1, 'nwdp', {writable: false, value: 'locked'});

let obj2 = {
    method() {
        super.dp = 200; // Overrides it
        super.ap = 'bird'; // Doesn't override it; Invokes setter, ap at obj1

        // Doesn't override it; But doesn't throw any kind of error as if it doesn't do anything
        super.nwdp = 'unlocked';
        }
};
Object.setPrototypeOf(obj2, obj1);
obj2.method();

Using Object.getOwnPropertyNames(obj2) shows properties dp, x and method on the object, obj2.

So, the question actually is why does JS behave like this?
In both the scenarios, it behaves exactly the same except when it is about overriding a non-writable data property on the child object using super.prop = <any_value>, it throws TypeError for properties meant to be defined for a class instance whereas it seems like it ignores the same statement in case of object literals.

Is this intentional? If so, what’s the reason behind it? Is it documented anywhere?

2

Answers


  1. All parts of class bodies are "strict" mode code, and that’s when you get that Type Error. Your second example is not "strict". You can make it so if you want, and then you’ll get the error.

    "use strict"; // has to be start of script file or function
    
    let obj1 = {
        dp: 'writable',
    
        get ap() { return this.x; },
        set ap(arg) { this.x = arg; }
    };
    Object.defineProperty(obj1, 'nwdp', {writable: false, value: 'locked'});
    
    let obj2 = {
        method() {
            super.dp = 200; // Overrides it
            super.ap = 'bird'; // Doesn't override it; Invokes setter, ap at obj1
    
            // Doesn't override it; But doesn't throw any kind of error as if it doesn't do anything
            super.nwdp = 'unlocked';
            }
    };
    Object.setPrototypeOf(obj2, obj1);
    obj2.method();
    
    Login or Signup to reply.
  2. The distinction is not between class methods and object literal methods but rather between strict mode and sloppy mode. That assignments to non-writable properties are silently ignored is a legacy behaviour, it should not happen in modern code that does "use strict" mode.

    Of course, everything in ES6 class definitions is strict by default, whereas normal top-level code is sloppy by default for compatibility reasons. You should use ES6 modules, or always begin your scripts with the "use strict"; directive.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search