如何避免 ES6 Javascript class 继承命名冲突

How to avoid ES6 Javascript class inheritance naming collisions

如何避免 ES6 Javascript 中 class 继承的命名冲突?

大型 ES6 Javascript 应用程序使用大量继承,以至于在基础 class 中使用通用名称可能意味着稍后在创建派生 classes 时会出现令人头疼的调试问题。这可能是 class 设计不佳的产物,但似乎更多是 Java 脚本能够顺利扩展的问题。其他语言提供了隐藏继承变量 (Java) 或属性 (C#) 的机制。缓解此问题的另一种方法是使用 Javascript 没有的私有变量。


下面是此类碰撞的示例。一个 TreeObject class 扩展了一个事件对象(以继承事件功能)但它们都使用 parent 来存储它们的父对象。

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

虽然这个例子有点人为,但我在像 Ember 这样的大型库中遇到过类似的冲突,其中库和最终应用程序之间的术语有很多重叠,导致我到处浪费时间。

这似乎确实是一个设计问题(使用较小的对象和更扁平的层次结构),但也有解决您的问题的方法: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;
    }
}

它们将可靠地防止任何冲突,因为所有符号都是唯一的,无论它们的描述符如何。

我能想到的最小化这种情况的一种可能方法是包装对象的内部状态,例如:

class Evented {
    constructor(parent) {
        this.eventedState = {
           parent : parent
        }

    }
}

并对所有 类 应用相同的模式。显然这仍然意味着每个对象有一个 属性 可能发生碰撞,但它减少了发生碰撞的机会。这样做的缺点是它并不完美,并且重构您的代码以使用此模式可能会非常痛苦。

Large ES6 JavaScript applications use a lot of inheritance.

实际上,他们中的大多数人都没有。在像 Angular 这样的框架中,平台定义的 classes 的一级继承是最常见的。深度继承结构很脆弱,最好避免。一个大型应用程序可能有两个用户级别的情况 classes,A > B,其中 A 包含一堆通用逻辑,B1 和 B2 是轻型专业化,例如组件的不同模板,在不会引起碰撞担忧的水平。

您的 Evented 示例在语义上并不是真正的父 class;它更像是mixin的本质。由于 JS 并不能很好地处理 mixin,我不会从 Evented 派生 Tree,而是将事件对象保存为 属性:

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

如果你真的想把Evented设计成类似mixin的superclass,那么设计者有责任让成员变量尽可能唯一,如

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

或使用另一个答案中建议的符号。或者,考虑使用地图:

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

我没有。 Ember classes 通常定义很少的成员,并且它们定义的方法定义明确且众所周知。

当然,真正的解决方案是使用打字系统,例如 TypeScript。它确实提供私有 members/methods。如果一个方法确实需要 public,TS 不会让你在 subclass 上定义一个同名的方法,除非签名匹配。