Peter T.
Peter T.

Reputation: 3315

Best practices for prototypal / prototype-based inheritance in newer JavaScript (ES6) & TypeScript?

Here are a couple of older question that discuss the Javascript prototypal inheritance & delegation, e.g.:

I am wondering what the current (2018) recommendation is to use prototypes / prototypal inheritance in Javascript.

As far as I understand, newer version of JavaScript (ES6) and TypeScript they both head more towards traditional class-based inheritance. (Myself I didn't use ES6 oder TS in practice yet.) Is this observation true?

In fact, this class-based code is really simple and easy to understand:

class A { a: "a" }
class B extends A { b: "b" }
let a = new A(), b = new B();

EDIT 2: In TypeScript it would be:

class A { a = "a" }
class B extends A { b = "b" }
let a = new A(), b = new B();

EDIT: In fact, the ES6 syntax is more complex:

class A { constructor() { this.a = "a"; } }
class B extends A { constructor() { super(); b = "b"; } }
let a = new A(), b = new B();

For using prototypes, there are more options, and actually I didn't find one yet that is equally simple and "nice".

EDIT: What I want to achieve is that I create b as instance of B with prototype A in a way that when I change A's property dynamically, b also is affected by the change:

A simple approach is:

var A = { a: "a" }
var B = Object.create(A, {b: {value: "b"}});
var a = Object.create(A), // direct instance of A
    b = Object.create(B); // indirect instance of A
console.log(b.a); // "a"
A.a = "a++"; // change the base prototype (will affect B, a, and b)
console.log(b.a); // "a++"

Which would be a lot nicer if the 2nd arg could also be a simple object with key-value pairs, not the property descriptors (see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create)

Most of the time, the constructor function are used, e.g. in https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain

function A() { this.a = "a"; }
function B() { this.b = "b"; }
B.prototype = new A();
var a = new A(), b = new B();
console.log(b.a); // "a"
A.a = "a++";
console.log(b.a); // return "a" instead of "a++" as b.a is overwritten in constructor

Also, not so nice, as here you cannot change A.a in a way that b.a is also changed which is IMO a key point in prototypal inheritance. So maybe this?

function A() {}
A.prototype.a = "a";
function B() {}
B.prototype = Object.create(A.prototype);
B.prototype.b = "b";
var a = new A(), b = new B();
function A() { this.a = "a"; }
function B() { this.b = "b"; }
B.prototype = new A();
var a = new A(), b = new B();
console.log(b.a); // "a"
A.a = "a++";
console.log(b.a); // still "a" instead of "a++"

Does not give the expected result. And, well, you don't want to write this, right?

Of course, you can put the creation in a constructor function as described by https://stackoverflow.com/a/16872315/1480587 but I think this is still not equally nice and simple to the class syntax. Actually, I'm looking for something like this (similar to Kotlin's object declaration):

object A { a: "a" }
object B extends A { b: "b" }
let a = new A(), b = new B();

So, what would you recommend? Is there anything that comes close?

Especially, if you want to use some encapsulation and have private object members not visible to cloned objects?

Does TypeScript provide a nice solution here?

Go for Kotlin?

Or should one generally move back to class-based inheritance as this is what everyone else is using and understands?

Upvotes: 1

Views: 2306

Answers (4)

Peter T.
Peter T.

Reputation: 3315

I just found what I was looking for: In ES6 "object literals are extended to support setting the prototype at construction." This is really simple & convenient!!

var obj = {
    // Sets the prototype. "__proto__" or '__proto__' would also work.
    __proto__: theProtoObj,
    // Computed property name does not set prototype or trigger early error for
    // duplicate __proto__ properties.
    ['__proto__']: somethingElse,
    // ...
};

Upvotes: -1

Peter T.
Peter T.

Reputation: 3315

Alternatively, underscore and lodash provide _.create(), which help with creating a new object with initialized prototype:

var a = { a: "a" };
var b = _.create(a, { b: "b" });
console.log(b.a); // "a"
a.a = "a++";
console.log(b.a); // "a++"

Upvotes: 0

Peter T.
Peter T.

Reputation: 3315

Maybe I play around with a simple helper function like this:

/**
 * Create a new object with `prototype` as its prototype.
 * The (optional) properties will be copied to the newly created object
 * This is similar to Object.create(), however the properties passed is a plain
 * object, not a object with property descriptors,
 * see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create
 *
 * @param prototype (object)
 * @param properties (object)
 * @return {prototype}
 */
function clonePrototype(prototype, properties) {
    var pDef = {};
    for(var key in (properties||{})) {
        if (properties.hasOwnProperty(key)) { pDef[key] = {value: properties[key]}; }
    }
    return Object.create(prototype, pDef);
}

This way, I can do what I wanted:

var a = { a: "a" };
var b = clonePrototype(a, { b: "b" });
console.log(b.a); // "a"
a.a = "a++";
console.log(b.a); // "a++"

.... comments and suggestions are welcome.

Upvotes: 0

Bergi
Bergi

Reputation: 664620

A simple approach is Object.create

Then use that. It seems to be enough for what you want to do - two objects, the one inheriting from each the other. You don't need any constructors to do initialisation, and therefore no class syntax either.


Btw, to simplify I would not use the second argument to Object.create when you don't need custom property descriptors. Just do

var B = Object.create(A);
B.b = "b";

or, in one expression,

var B = Object.assign(Object.create(A), {
  b: "b",
});

Upvotes: 3

Related Questions