6

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

ibrahim mahrir
  • 31,174
  • 5
  • 48
  • 73
jbandi
  • 17,499
  • 9
  • 69
  • 81
  • Why are you trying to redefine the ENTIRE prototype object? Why would you ever want to do that after you've already defined part of the first prototype? You can assign individual properties to the existing prototype object just fine. This doesn't seem like a real world problem/issue at all so not really worth even trying to understand. – jfriend00 May 26 '18 at 15:06
  • Did you check to see if `Pet.prototype` might not be configurable when created with `class` so that it can't be entirely replaced (since there would generally not be a reason to replace the entire object because that would nullify the entire `class` definition)? – jfriend00 May 26 '18 at 15:12
  • That's as the answer suggests. You can always use Object.assign to override several prototype props (that's how it's often done), but be aware of get/set accessors because they are defined as property descriptors. – Estus Flask May 26 '18 at 15:22
  • 2
    Classes are not entirely syntactic sugar to regular functions as constructors. There are [differences](http://stackoverflow.com/questions/46820121/in-javascript-what-are-the-differences-between-a-class-and-a-constructor/46821201#46821201). – MinusFour May 26 '18 at 15:29
  • Yeah, classes are "syntax sugar" in that they can be "desugared" into code that would behave identically without `class` syntax, that doesn't mean that they are identical to how you'd have written it in ES5. – loganfsmyth May 26 '18 at 18:06

1 Answers1

7

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?

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • It's the **writable** property that makes it "not writable" not the **configurable** one. – ibrahim mahrir May 26 '18 at 15:32
  • I've removed the `"use strict";` from the examples in the question so that the error is not thrown in the latter. Now you can compare the results of the call to `Object.getOwnPropertyDescriptor` of both examples. You can see that both example have `configurable` set to `false` which proves it is not the one. However, the `writable` property differ. – ibrahim mahrir May 26 '18 at 15:36
  • You can still overwrite a non-writable prop, not a non-configurable. – MinusFour May 26 '18 at 15:37
  • @MinusFour No, you can't. – ibrahim mahrir May 26 '18 at 15:40
  • @ibrahimmahrir - I updated my answer to describe the `writable` property as the issue here. – jfriend00 May 26 '18 at 15:42
  • I've added ES 2015 specification references that show why a prototype created via the `class` keyword has a non-writable prototype. – jfriend00 May 26 '18 at 15:43
  • @ibrahimmahrir you can overwrite it (the prop, not the value of the prop) just not through `=`. Here's [an example](https://jsfiddle.net/o3e47uLf/). Non-configurable means you can't overwrite it at all. – MinusFour May 26 '18 at 15:53
  • @MinusFour Then why is the first example from the question working? It is `configurable: false`, isn't it? – ibrahim mahrir May 26 '18 at 15:55
  • 1
    @ibrahimmahrir Ah, I see your point the difference is indeed the writable part of the prop. – MinusFour May 26 '18 at 16:20
  • @jbandi - Added reference to [another reference](https://stackoverflow.com/questions/46820121/in-javascript-what-are-the-differences-between-a-class-and-a-constructor/46821201#46821201) that summarizes some other differences when using `class`. – jfriend00 May 26 '18 at 16:59
  • This is BRILLIANT! This very question just came up on FreeCodeCamp, and it was very cool to find that the answer was so clear and concise. Thank you! I wanna upvote this one TWICE. – Snowmonkey Jan 18 '19 at 17:36