Hacker News new | ask | show | jobs
by pluma 3034 days ago
The class system is a bit hit and miss. In practice you will be fine if you're using classes and you will be fine if you use prototypes but you should avoid mixing them directly.

Strictly speaking, there is no essential difference because it's entirely possible to implement classical inheritance using prototypal inheritance -- just not the other way around.

There's no classical equivalent of this, for example:

    foo = {hello: "world"};
    bar = Object.create(foo);
    console.log(bar.hello); // "world"
Note how bar has no "hello" property, so the property is looked up on its prototype, foo.

Your code uses the "new" keyword. That keyword is pretty much emulating classical behaviour on top of the prototypal inheritance. The following two examples are equivalent:

    foo = new Foo(1, 2, 3);
and

    foo = Object.create(Foo.prototype);
    Foo.call(foo, 1, 2, 3);
Every function implicitly has a "prototype" property which is set to an object with a "constructor" property set to the function itself.

Object.create creates a new object and sets its prototype (the internal [[Prototype]] property, not the "prototype" property on the function) to its argument.

There's a bit more going on underneath (mostly to allow certain optimisations) but conceptually even the "new" keyword is just syntactic sugar.

1 comments

To be clear: in practice there are three ways to use inheritance in JS:

1) Prototypes with constructors using the "new" keyword (ES3+):

    function Foo (name) {
      this.name = name;
    }
    Foo.prototype.hello = function () {
      return `Hello ${this.name}!`;
    };
    let foo = new Foo("world");
    console.log(foo.hello()); // Hello world!
2) Plain prototypes with Object.create (ES5+):

    let bar = {
      name: 'world',
      hello: function () {
        return `Hello ${this.name}!`;
      }
    };
    let baz = Object.create(bar);
    baz.name = 'Wisconsin';
    console.log(bar.hello()); // Hello world!
    console.log(baz.hello()); // Hello Wisconsin!
3) Classes (ES2015+):

    class Qux {
      constructor (name) {
        this.name = name;
      }
      hello () {
        return `Hello ${this.name}!`;
      }
    }
    let qux = new Qux("world");
    console.log(qux.hello()); // Hello world!
Note how all three examples do (roughly) the same thing but in slightly different ways.

Inheritance in #1 is a bit awkward (which is why before ES2015 there were myriads of different inheritance libraries).

In #2 it's fairly straightforward but type checks are unintuitive because there really aren't any types involved (instanceof requires a constructor).

In #3 both inheritance and type checks are fairly straightforward but it also uses more abstractions, which can make it more difficult to understand conceptually (especially when coming from a language like Java or C# which is syntactically similar).

IMO #1 is the worst of both worlds because the behaviour of the "new" keyword and the special "prototype" property are difficult to understand and the constructor looks like a regular function but expects to be called in a special way (using the "new" keyword) and will break in unexpected ways if called as a normal function:

> TypeError: Cannot set property 'name' of undefined

(or worse: outside strict mode it might not fail and actually try to write to the global scope -- so remember to always "use strict")

While the value of a class is also a constructor function, calling it without "new" will break with a distinct error that makes the mistake obvious and easy to understand:

> TypeError: Class constructor Qux cannot be invoked without 'new'

Thanks :-)

I've never used Object.create for inheritance (although I heard it was a favorite of Douglas Crockford's at some point). And the syntactic sugar of es2015 classes is so appealing I never wrote constructor functions on a regular basis.

What's so conceptually difficult about es2015 classes that confuses people? They always felt intuitive to me. A bit restrictive perhaps, because you don't have private methods, or instance properties, and if you want to call super in a method you have to do it before anything else, but other than that — nothing especially confusing.