Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to avoid ES6 Javascript class inheritance naming collisions

How does one avoid naming collisions from class inheritance in ES6 Javascript?

Large ES6 Javascript applications use a lot of inheritance, so much so that using generic names in a base class can mean debugging headaches later when creating derived classes. This can be a product of poor class design but seems more to be a problem with Javascript being able to scale up smoothly. Other languages provide mechanisms to hide inherited variables (Java) or properties (C#). Another way to alleviate this problem is to use private variables which isn't something Javascript has.


Here's an example of such a collision. A TreeObject class extends an Evented object (to inherit evented functionality) yet they both use parent to store their parent.

class Evented {
    constructor(parent) {
        this.parent = parent;
    }
}
class TreeObject extends Evented{
    constructor(eventParent, treeParent) {
        super(eventParent);
        this.parent = treeParent;
    }
}

While this example is a bit artificial, I've had similar collisions in large libraries like Ember where terminology between the library and the end application overlap quite a bit causing me hours wasted here and there.

like image 298
Cobertos Avatar asked May 04 '26 01:05

Cobertos


2 Answers

This really seems to be a design issue (use smaller objects and flatter hierarchies), but there's a solution to your problem as well: symbols!

const parentKey = Symbol("parent");
export class Evented {
    constructor(parent) {
        this[parentKey] = parent;
    }
}

import {Evented} from "…"
const parentKey = Symbol("parent");
class TreeObject extends Evented {
    constructor(eventParent, treeParent) {
        super(eventParent);
        this[parentKey] = treeParent;
    }
}

They will reliably prevent any collisions, as all symbols are unique, regardless of their descriptors.

like image 120
Bergi Avatar answered May 06 '26 21:05

Bergi


Large ES6 JavaScript applications use a lot of inheritance.

Actually, most of them don't. In a framework like Angular, one level of inheritance from the platform-defined classes is most common. Deep inheritance structures are brittle and best avoided. A large app might have some cases of two user levels of classes, A > B, where A contains a bunch of common logic and B1 and B2 are light specializations, such as a different template for a component, at a level that does not give rise to concerns about collisions.

Your example of Evented is semantically not really a parent class; it's more in the nature of a mixin. Since JS doesn't really handle mixins well, instead of deriving Tree from Evented, I'd hold the evented object as a property:

class TreeObject {
    constructor(eventParent, treeParent) {
        this.evented = new Evented(eventParent);
        this.parent = treeParent;
    }
    send(msg) { this.evented.send(msg); }
}

If you really want to design Evented for use as a mixin-like superclass, then it's the responsibility of the designer to make member variables as unique as possible, as in

export class Evented {
    constructor(parent) {
        this.eventedParent = parent;
    }
}

or use a symbol, as proposed in another answer. Or, consider using a map:

const parents = new Map();

class Evented {
  constructor(parent) {
    parents.set(this, parent);
  }
  sendParent(msg) {
    parents.get(this).send(msg);
  }
}

I've had similar collisions in large libraries like Ember

I haven't. Ember classes typically define few members, and the methods they define are well-defined and well-known.

Of course, the real solution is to use a typing system such as TypeScript. It does provide private members/methods. If a method does need to be public, TS will not let you define a method by the same name on the subclass unless the signatures match.


Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!