jbandi
jbandi

Reputation: 18169

ES2015 Classes prevent setting the prototype

I discovered that ES2015 classes prevent setting (redefining) their prototype.

It is often claimed that ES2015 classes are just "syntactic sugar" on top of ES5 constructor functions and prototype based inheritance.
But this is a difference in behavior...

Is this behavior part of the ES2015 specification? I did not find any documentation about this ...

The following examples illustrate the difference:

function Pet() {}
Pet.prototype.eat = () => {
  console.log('Pet is eating ...');
}

Pet.prototype = {eat: function(){console.log('Pet is REALLY eating ...')}};

const pet = new Pet();
pet.eat();     // -> Pet is REALLY eating ...

console.log(Object.getOwnPropertyDescriptor(Pet, 'prototype'));

=> Redefining prototype of Pet works

class Pet {
  eat() {
    console.log('Pet is eating ...');
  }
}


Pet.prototype = {eat: function(){console.log('Pet is REALLY eating ...')}};

const pet = new Pet();
pet.eat();     // -> Pet is eating ...

console.log(Object.getOwnPropertyDescriptor(Pet, 'prototype'));

=> Redefining prototype of Pet does not work

Any pointers to documentation of this behavior would be appreciated ...

Upvotes: 6

Views: 284

Answers (1)

jfriend00
jfriend00

Reputation: 707318

The difference here is that when created with class, the prototype object is set to writable: false so that you can't replace Pet.prototype with assignment. You can see that difference in your Object.getOwnPropertyDescriptor() call when you remove strict mode.

The OP's first code example shows this:

{
  ...
  "writable": true,
  "enumerable": false,
  "configurable": false
}

The OP's second code example shows this:

{
  ...
  "writable": false,
  "enumerable": false,
  "configurable": false
}

The writable property determines whether the property Pet.prototype can be assigned a new value or not (e.g. replaced with a new object). A false value means that you cannot replace Pet.prototype using assignment. So, when your code tries to do that, it fails.

You are still free to add or remove individual properties from the prototype object, just not replace the entire object. This makes some sense to me because replacing the entire prototype object undoes the entire class definition. You could probably change the Pet.prototype object to be writable if you had a real world reason to replace it.

This is described in the ES 2015 specification. In 14.5.14 Runtime Semantics: ClassDefinitionEvaluation, at step 16, it does this:

  1. Perform MakeConstructor(F, false, proto).

That false argument is making the prototype that is created non-writable (so it can't be replaced).

And, if you then look at 9.2.8 MakeConstructor(), you see this in step 6:

Let status be DefinePropertyOrThrow(F, "prototype", PropertyDescriptor{[[Value]]: prototype, [[Writable]]: writablePrototype, [[Enumerable]]: false, [[Configurable]]: false}).

Where the writable attribute is getting the false value that was previously passed to to it.


It is often claimed that ES2015 classes are just "syntactic sugar" on top of ES5 constructor functions and prototype based inheritance. But this is a difference in behavior...

While the general operation of the methods defined in the class is pretty much the same, this other answer describes several other differences between using class versus manually assigning to the prototype: In javascript, what are the differences between a class and a constructor?

Upvotes: 8

Related Questions