Search code examples
javascriptinheritanceprototypejses6-class

Why is inheritance using constructor functions "hard to do properly" (MDN)?


The MDN's article for "Inheritance and the prototype chain", under "Different ways of creating and mutating prototype chains": "With constructor functions", gives the following example:

function Graph() {
  this.vertices = [];
  this.edges = [];
}

Graph.prototype.addVertex = function (v) {
  this.vertices.push(v);
};

...

And underneath it says:

Constructor functions have been available since very early JavaScript. Therefore, it is very fast, very standard, and very JIT-optimizable. However, it's also hard to "do properly" because methods added this way are enumerable by default, which is inconsistent with the class syntax or how built-in methods behave. Doing longer inheritance chains is also error-prone, as previously demonstrated.

~~~ EDIT: ~~~

Ok I understand the difference between this example and the class equivalent is that in the constructor function approach, the methods on the prototype are enumerable:

for (const prop in Graph.prototype) {
    console.log(prop);
} // -> addVertex

If I rewrite this using the class syntax, they are not:

class GraphCls {
  vertices = [];
  edges = [];
    
  addVertex (v) {
      this.vertices.push(v);
  }
}

for (const prop in GraphCls.prototype) {
    console.log(prop);
} // -> nothing

But what is the problem with methods on the prototype being enumerable vs not being enumerable? Why is "not enumerable" the "proper way".


Solution

  • Why is "not enumerable" the "proper way"?

    The article already explains in the same sentence: it "is inconsistent with the class syntax or how built-in methods behave". Other than that, there is no good reason to make methods non-enumerable. I think it's a bit bold by that MDN author to call a decade-old best practice "improper", and there are other questionable statements in that article.

    What is the problem with methods on the prototype being enumerable vs not being enumerable?

    None, really. As you demonstrated, it only matters when someone uses a for…in loop on the instance, which nobody should do anyway.