2016-12-15 102 views
3

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

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


这是一个这样的碰撞的例子。 TreeObject类扩展了一个Evented对象(以继承已有的功能),但它们都使用parent来存储它们的父项。

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

虽然这个例子是有点做作,我已经在像灰烬大型图书馆类似的冲突在图书馆和最终应用之间的术语相当多的重叠导致我在这里和那里浪费时间。

回答

6

这似乎是一个设计问题(使用更小的对象和更平坦的层次结构),但是您的问题也有一个解决方案:符号

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

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

+3

此问题的唯一*目标*答案。 –

+0

我甚至可以看到这是一个很好的私有变量仿真,如果有问题的符号是文件本地的。 – Coburn

+0

@Coburn不完全是,每个人都可以枚举任意对象的符号键属性,就像使用字符串键的属性一样。这只是将Object.getOwnPropertyNames改为Object.getOwnPropertySymbols的问题。 – Bergi

0

尽量减少这一点,我能想到我的头右顶部,被包装的对象的内部状态,为您的示例的一种可能的方式是:

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

    } 
} 

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

2

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

其实大部分都没有。在像Angular这样的框架中,平台定义的类的一种继承是最常见的。深层的继承结构是脆弱的,并且最好避免。一个大型的应用程序可能有两个用户级别的类A> B,其中A包含一堆公共逻辑,而B1和B2是轻型特化,比如组件的不同模板,其级别不会给引起对碰撞的担忧。

Evented的例子在语义上并不是真正的父类;它更多是混合的本质。由于JS并没有真正处理好混入,而不是从Evented获得Tree,我会保持事件触发对象作为一个属性:

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

如果你真的想设计Evented用作混入般的超,那么它的设计使成员变量尽可能独特的责任,如

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类通常定义了很少的成员,他们定义的方法是明确定义的并且是众所周知的。

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

+0

在Ember碰撞中,我最近与Ember的碰撞是在组件上有一个'renderer'属性(用于存放一个WebGL渲染器),最终与一些内部的Ember属性相冲突(我认为它是私有的,但我看不到它在文档中)。我有'send'和'sendAction'类似的问题,但这两个都有很好的文档。两次对我来说太过分了。 – Coburn