【问题标题】:Javascript classes : how to access overridden parent class functions in parent class codeJavascript 类:如何在父类代码中访问被覆盖的父类函数
【发布时间】:2018-03-16 03:42:40
【问题描述】:

Javascript ES6(节点 8.4.0 和最新的 chrome 和最新的 Firefox)

我期待

class Parent {
    init(){
        console.log("Parent init") ;
        this._surname = "McClass" ;
    }
    constructor() {
        console.log("Parent constructor") ;
        this.init();
    }

    get surname(){
        return this._surname ;
    }
}

class Child extends Parent {

    init(){
        console.log("Child init") ;
    }
    constructor() {
        super();
        console.log("Child constructor") ;
        this.init();
    }    
}

var child = new Child() ;

console.log(child.surname);

给出以下输出;

Parent constructor
Parent init
Child constructor
Child init
McClass

(这是可比较的 C++ 代码给出的)

唉,我明白了;

Parent constructor
Child init
Child constructor
Child init
undefined

我是不是做错了什么,或者这是正确的预期行为,如果是这样,如何证明是合理的?

编辑;

请参阅下面的 MinusFour 回答,了解如何实现我正在尝试做/期望的事情。

至于为什么观察到的输出是“正确”的行为和合理的;

正如 Bergi 指出的(在 cmets 中),js 中对对象方法的所有调用实际上都是“虚拟的”(添加到对象原型继承链中的该名称的最后一个方法是第一个找到并因此执行的)。事实证明,调用在类构造上下文中仍然有效。

C++ 在构造过程中不应用虚方法行为,但 Java 再次应用,并且您在可比较的 Java 代码中获得相同的输出(如上),因此观察到的行为有先例。

【问题讨论】:

  • 我不会在 child 中覆盖 init,只需将其称为其他名称。
  • @smnbbrv ,您能否详细说明或提供指向源的链接,解释为什么它是正确的或将这种行为定义为正确的?
  • @MinusFour,但是如果我从第 3 方类继承子类而不知道我正在使用父类中使用的名称会发生​​什么 - 然后我会得到我什至可能没有意识到正在发生的意外行为。
  • 你为什么使用什么都不做的getter?如果您只想拥有一个点可访问属性,请添加它。如果您稍后改变主意,您可以在不更改调用代码的情况下重构为 getter。
  • JavaScript 中没有“私有”属性/方法的概念。一切都是公开的。用其他语言扩展类时,你不知道公共方法吗?

标签: javascript class inheritance ecmascript-6 overriding


【解决方案1】:

你可以这样做:

Parent.prototype.init.call(this);

class Parent {
  init() {
    console.log("Parent init");
    this._surname = "McClass";
  }
  constructor() {
    console.log("Parent constructor");
    Parent.prototype.init.call(this);
  }

  get surname() {
    return this._surname;
  }
}

class Child extends Parent {

  init() {
    console.log("Child init");
  }
  constructor() {
    super();
    console.log("Child constructor");
    this.init();
  }
}

var child = new Child();

为了确保它永远不会被覆盖,但我建议你一开始就不要覆盖它。

【讨论】:

  • 看起来我自己的代码有可能 - 以后可能会接受答案(需要考虑的事情)。
  • 接受,因为它回答了“如何”。 (怀疑我永远不会得到一个合理的解释“为什么”)干杯!
  • @Bob,“为什么”将是一个只有 TC39 才能回答的设计问题。我最好的选择是他们不希望有与目前不同的行为。
  • @Bob 为什么?因为 JS 是高度动态的,每个方法调用/属性访问都是virtual.
  • @Bob,如果没有class,你仍然会遇到the same problem。因此,如果您认为基于构造函数的方法没问题,那么 class 也应该没问题。除非您期待使用 class 关键字的不同继承模型。
【解决方案2】:

这是预期的行为,因为它可以在遵循规范的既定 ES6 类实现中看到。

this指的是当前类实例,在Child被实例化的情况下是Child的一个实例——即使在Parent类中,因为只有一个实例,它是instanceof Child .

如果Child 覆盖该方法,则它有责任提供调用它的机制。考虑到 init 遵循一些记录在案的约定,并且是发生类初始化以使构造函数更精简和更可测试的地方,它是:

class Parent {
    init(){...}
    constructor() {
        this.init();
    }
    ...
}

...

class Child extends Parent {
    init(){
        super.init();
        ...
    }
    // optional, as long as `init` contains all init logic
    constructor() {
        super();
    }    
}

产生一个序列:

Parent constructor
Parent init
Child init
Child constructor

如果init 应该在两个类中完全独立工作,则不应覆盖它。方法的名称应该不同,例如initParentinitChild。或者可以使用任何其他方式来避免命名冲突,例如:

const init = Symbol('Parent init');
class Parent {
    [init](){...}
    constructor() {
        this[init]();
    }
    ...
}

...

const init = Symbol('Child init');
class Child extends Parent {
    [init](){...}
    constructor() {
        this[init](); // totally independent method
    }
}

【讨论】:

  • 基本 oo 原则 - 孩子不需要了解其父母的内部情况。
  • @Bob 不需要。开发人员会。如果你遵循一些关于 init 方法的约定,那就应该这样做。您应该有机会在孩子中覆盖或增加父 init。如果不知道这段代码的上下文,很难说这是否是最好的解决方案。
  • 当我说“孩子不需要知道其父母的内部结构”时,我的意思是它不需要引用那些内部结构,即编写孩子的开发人员不需要了解它们 - 应该不需要对父类的内部进行约定。类(开发人员或子类)的消费者唯一需要知道的是父类的接口。
  • 在这种情况下,开发人员应该了解它们。这里init 是一种遵循约定的钩子,应该记录它的工作方式。我以前使用过这个配方,它确实为类构造增加了可测试性和一些灵活性。如果 Parent 和 Child init 应该完全独立地调用,这意味着该方法根本不应该被覆盖。应该有不同的initParentinitChild 方法(或应使用任何其他避免方法名称冲突的方法)。
  • 即使这个原则(尽管原则并不是一成不变的,因为它们总是出于实际考虑)在 C++ 中对你有用,但这并不意味着它适用于 JS - 因为显然它不't 并导致在另一个答案中显示的Parent.prototype.init hack。我更新了答案以涵盖不相关的“init”方法。
猜你喜欢
  • 1970-01-01
  • 2011-01-29
  • 1970-01-01
  • 2021-05-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-22
相关资源
最近更新 更多