【问题标题】:ES6, access props set by the child from the parent's constructorES6,从父的构造函数访问子设置的props
【发布时间】:2016-11-18 12:11:01
【问题描述】:

我正在尝试使用 ES6 类建立一个类层次结构。

所有实体都将继承自单个 Base 类。它将需要以通用方式访问Child 类公开的属性。像这样:

class Base {
    constructor() {
        this.doSomethingWithProps();
    }

    doSomethingWithProps() {
        console.log(Object.keys(this));
    }
}

class Entity extends Base {
    constructor() {
        super();

        this.key = "value";
    }
}

显然,在上面的示例中,Base 类将看不到由 Entity 设置的 key 属性。理想情况下,我会将this 分配移到super() 之前,但这是不允许的。能够在构造函数之前设置属性也很好,但是AFAIK,这也是不可能的。

我剩下的唯一解决方案是在每个 Entity 中执行以下操作:

class Base {
    doSomethingWithProps() {
        console.log(Object.keys(this));
    }
}

class Entity extends Base {
    constructor() {
        super();

        this.key = "value";

        this.doSomethingWithProps();
    }
}

但是,除了不太理想之外,如果我想从Entity 继承也会产生问题。 doSomethingWithProps 然后需要能够检测它是否是调用层次结构中的“最顶层”方法,然后才做它的事情。实现那个(我能想到的)的唯一方法将涉及甚至更多样板。

我在这里缺少一些解决方案吗?如果需要,我愿意使用不同的 OOP 模式,但我希望尽可能接近原生 ES6 类。

【问题讨论】:

  • 通常最好依赖组合而不是继承,因为它可能导致脆弱的基类。您仍然可以使用类,但在其中注入另一个包含您需要的方法的类。如果您确实走课程路线,这也可能会有所帮助:medium.com/@dan_abramov/…
  • 如果基类知道派生类,那么就大错特错了。
  • @zeroflagL 它不完全是,只是通用初始化逻辑。例如。强制所有属性,验证没有分配任何非法内容等。由于 js 的动态特性,必要的健全性检查。

标签: javascript oop design-patterns ecmascript-6


【解决方案1】:

你想要做的事情是完全不可能的。父初始化总是在子初始化之前运行,因此父构造函数必须不依赖于可能被子覆盖的属性。顺便说一句,这是一个与语言无关的问题。

解决方法是给构造函数使用参数,可以在child到达父代码之前修改:

class Base {
    constructor(key) {
        this.key = key;
        // do something with key
    }
}

class Entity extends Base {
    constructor() {
        super("value");
    }
}

console.log(new Entity);

或更通用的

class Base {
    constructor(props) {
        this.doSomething(props);
        Object.assign(this, props);
    }

    doSomething(props) {
        return Object.keys(props);
    }
}

class Entity extends Base {
    constructor() {
        super({key: "value"});
    }
}

console.log(new Entity);

还要注意构造函数应该始终是纯的。它们的唯一目的是从参数初始化新实例,并且不做任何其他事情,比如执行一些副作用。构造函数通常不需要调用任何(可重写)实例方法(静态方法也可以)。
因此,如果您在创建实例时仍需要执行类似的操作,请不要在构造函数中执行此操作。而是在使用构造函数后调用方法:

class Base {
    log() {
        console.log(Object.keys(this));
        return this;
    }
}

class Entity extends Base {
    constructor() {
        super();
        this.key = "value";
    }
}

var e = new Entity().log();

你也可以把它抽象出来:

class Base {
    static createAndLog(...args) {
        var x = new this(...args);
        x.log();
        return x;
    }
    …
}
…

var e = Entity.createAndLog();

【讨论】:

  • 在第一个模式中,Entity 类没有清晰的公共接口供 IDE-s、眼睛和 linter 挑选。工厂的第二个想法很好。缺点是你依赖于围绕类构建的特定特殊接口,它不如new 通用。
  • @panta82 如果您的意思是第二个带有Object.assign 的sn-p,是的,这可能是个问题。也许不使用Object.assign,但传递对象也分配给this.key。这完全取决于您需要对值做什么。一般来说,一个类不需要知道其子类的附加属性。
  • @panta82 关于第二种模式,你仍然可以使用new,但是如果你想使用你的特殊功能,你也必须在某个时候调用.log()(不一定需要在实例化之后立即出现)。同样,这取决于该方法会做什么; createAndLog 工厂只能帮助避免一些代码重复,但并不总是有用。
【解决方案2】:

根据您想走的复杂程度,可能有两条路线可以走。

1。保持状态在Base

如果可能,将所需的道具从 Entity 传递到 Base 并在那里保持状态。

class Base {
    constructor(props) {
        for (var k in props) {
          this[k] = props[k]
        }
        this.doSomethingWithProps();
    }

    doSomethingWithProps() {
      console.log(Object.keys(this));
    }
}

class Entity extends Base {
    constructor(props) {
        super(props);
    }
}

let entity = new Entity({key: 'value'})
// ["key"]

2。将对Entity 的引用传递给Base

注意,我错误地假设了实体的单独实例。这过于多余。

没有那么简单,并且在技术上将包含一个自引用键,因为 Entity 将从 Base 继承 child 键。这肯定会被认为是一种“错误”的方法,但我将其包括在内。

class Base {
    constructor() {
      this.child
    }
    
    registerChild(child) {
      this.child = child
      this.doSomethingWithProps()
    }
    
    doSomethingWithProps() {
      console.log(Object.keys(this.child));
    }
}

class Entity extends Base {
    constructor() {
        super();
        this.key = 'value'
        this.registerChild(this)
    }
}

let entity = new Entity()
// ["key", "child"]

【讨论】:

  • 您的第二个 sn-p(带有 registerChild 的那个)与从 Entity 构造函数调用 this.doSomethingWithProps() 的问题完全相同(请参阅问题)
  • @Bergi 好点。我想我更多的是从两个单独的实例需要访问Entity,而不是继承。
  • 1 的问题是 Entity 将被 IDE 视为空类。没有智能感知 :(@Bergi 指出了 2 的问题
【解决方案3】:

我认为这里的观察应该是:避免在构造函数中调用依赖于对象状态的方法,因为构造函数旨在构建该状态。

【讨论】:

  • 问题是当你有派生状态。不错的理论,但在现实中分崩离析。
【解决方案4】:

其实你想问的是关于js语法的问题,但是问题的根源是oop

的思想

在您写作时考虑extends 的含义:

Entity extends Base

意思是Entity使用了Base的东西,关键字是extends而不是communicate之类的东西,这意味着Base 不应该关心Entity首先。 extends 也可以表示 Entity 有一些 Base 没有预料到的东西,因此是 extends

但是,从我自己的角度来看(如果我错了,请纠正我),有一些有效的用例,其中父类对子类有一定的认识。 例如:在Parent类中放入一些常用的逻辑来操作Child的属性,这种情况下,从逻辑(不是语法)的角度来看,子属性应该是static,即属于整个类而不是一个实例

【讨论】:

  • 那么你将如何实现父 communicates 与孩子?
猜你喜欢
  • 2017-06-28
  • 1970-01-01
  • 2017-04-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-05-06
  • 2019-06-14
  • 2018-06-24
相关资源
最近更新 更多