【问题标题】:How to avoid ES6 Javascript class inheritance naming collisions如何避免 ES6 Javascript 类继承命名冲突
【发布时间】:2017-05-01 11:09:37
【问题描述】:

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

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


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

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

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

【问题讨论】:

    标签: javascript inheritance ecmascript-6 es6-class


    【解决方案1】:

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

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

    【讨论】:

    • 这个问题唯一的objective答案。
    • 如果有问题的符号是文件本地的,我什至可以看到这是对私有变量的一个很好的模拟。
    • @Coburn 不完全是,每个人仍然可以枚举任意对象的符号键属性,就像您可以使用字符串键属性一样。只需将Object.getOwnPropertyNames 更改为Object.getOwnPropertySymbols
    • @Bergi Dang,仍然是一个简洁的功能。
    【解决方案2】:

    大型 ES6 JavaScript 应用程序使用大量继承。

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

    您的Evented 示例在语义上并不是真正的父类;它更多的是混合的性质。由于 JS 并不能很好地处理 mixins,我不会从 Evented 派生 Tree,而是将事件对象作为属性:

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

    如果您真的想将Evented 设计为类似mixin 的超类,那么设计者有责任使成员变量尽可能唯一,如

    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);
      }
    }
    

    我在 Ember 等大型库中也遇到过类似的冲突

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

    当然,真正的解决方案是使用 TypeScript 等类型系统。它确实提供了私有成员/方法。如果方法确实需要公开,除非签名匹配,否则 TS 不会让您在子类上定义同名方法。

    【讨论】:

    • 在 Ember 冲突中,我最近与 Ember 的冲突是在组件(用于保存 WebGL 渲染器)上有一个 renderer 属性,最终与一些内部 Ember 属性发生冲突(我认为它是私有的公元前我在文档中没有看到它)。我对sendsendAction 也有类似的问题,但是这两个都有很好的记录。两次对我来说太多了。
    【解决方案3】:

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-03-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-02
      • 1970-01-01
      相关资源
      最近更新 更多