【问题标题】:Javascript: Why does declaring a property in a subclass overwrite the same property in a super class as nullJavascript:为什么在子类中声明属性会覆盖超类中的相同属性为null
【发布时间】:2020-10-14 16:42:04
【问题描述】:

我的问题与理解类如何为属性赋值以及对象如何在 Javascript 中实例化有关。我想更多地了解这个过程是如何工作的。

如果我创建两个类,其中第二个继承自第一个

class A {
    name

    constructor(name){
        this.name = name
    }
}

class B extends A {
    name
    status

    constructor(name, status){
        super(name)
        this.status = status
    }
}

然后创建一个 B 类的实例,当我将它打印到控制台时

x = new B('myClass', true)
console.log(x)

它打印出名称变量未定义

B { name: undefined, status: true }

我很确定 B 中的 name 属性覆盖了 A 中的 name 属性,但是为什么 A 构造函数不将新的 name 变量分配给传递给它的值?

【问题讨论】:

    标签: javascript class inheritance


    【解决方案1】:

    目前(2020 年 10 月)这是正确的行为。

    当你设置时

    class A {
        name
    }
    

    这声明了一个类字段。这还不是标准,它是a proposal in stage 3。它可能会改变,但不会改变太多。第 3 阶段是候选阶段,可能包括完成改进。

    无论如何,根据提案的当前规范,您所看到的都是正确的。任何没有初始化器的类字段都设置为undefined。发生这种情况是因为您在 B 中有另一个名为 name 的类字段,没有初始化程序。父构造函数中发生的赋值将被覆盖。

    Here is the relevant part of the proposal that discusses this behaviour:

    没有初始化的字段被设置为undefined

    无论是否存在初始化程序,公共和私有字段声明都会在实例中创建一个字段。如果没有初始化程序,则该字段设置为undefined。这与某些转译器实现有些不同,后者将完全忽略没有初始化器的字段声明。

    例如,在以下示例中,new D 将生成一个对象,其y 属性为undefined,而不是1

    class C {
      y = 1; 
    } 
    class D extends C {
      y; 
    }
    

    将不带初始值设定项的字段设置为undefined 而不是擦除它们的语义是,字段声明提供了可靠的基础,以确保属性存在于创建的对象上。这有助于程序员将对象保持在相同的一般状态,这可以使其易于推理,有时在实现中更可优化。

    【讨论】:

    • 我在B 的构造函数中调用A 的构造函数。 name 的这个字段是在构造函数执行完成后创建并初始化为undefined 的吗?因为否则我希望 name 被初始化为 undefined 但随后 A 的构造函数将运行并覆盖此值
    • @Sam 是的,显然决定确保没有初始化器的类字段始终为 B 中的 undefined. So, calling super()` 将在 A 中初始化 this.name 但一次 @987654346 @ 然后属性被抹回undefined。在 super() 调用之后,似乎在没有初始化程序的情况下声明一个类字段隐含地表现得好像你有一个 name = undefined。我不确定我是否同意这个决定,但在隔离可能影响对象的更改方面有一些逻辑。如果A 没有name 但后来被更改为初始化它,它可能会改变B 的行为。
    【解决方案2】:

    这个例子是从MDN借来的,以表明这种行为不应该发生。

    class Animal { 
      constructor(name) {
        this.name = name;
      }
      
      speak() {
        console.log(`${this.name} makes a noise.`);
      }
    }
    
    class Dog extends Animal {
      constructor(name) {
        super(name); // call the super class constructor and pass in the name parameter
      }
    
      speak() {
        console.log(`${this.name} barks.`);
      }
    }
    
    let d = new Dog('Mitzie');
    d.speak(); // Mitzie barks.

    如果您在 SO 中的 sn-p 配置中选中 Use BabelJS / ES2015 复选框,则您的代码不会出现问题。

    class A {
        name;
        constructor(name){
            this.name = name;
        }
    }
    
    class B extends A {
        name;
        status;
    
        constructor(name, status){
            super(name);
            this.status = status;
        }
    }
    
    let a = new B('myClass',true);
    console.log(a);

    但是如果你取消选中它...

     class A {
            name;
            constructor(name){
                this.name = name;
            }
        }
    
        class B extends A {
            name;
            status;
    
            constructor(name, status){
                super(name);
                this.status = status;
            }
        }
    
        let a = new B('myClass',true);
        console.log(a);

    ...相同的代码提供了您注意到的错误。

    那么究竟是什么问题以及如何解决呢?无需在子类中再次设置name。如果你不这样做,结果如下:

     class A {
            name;
            constructor(name){
                this.name = name;
            }
        }
    
        class B extends A {
            status;
    
            constructor(name, status){
                super(name);
                this.status = status;
            }
        }
    
        let a = new B('myClass',true);
        console.log(a);

    【讨论】:

    • 是的,我无意两次声明名称,但这是我遇到的一个错误。你用 BabelJS / es2015 说这不是问题吗?这是否意味着我没有使用它们?我正在使用很多 es6 功能,例如 =>import。我很确定我也在使用 Babel。我正在运行一个反应应用程序并使用 webpack。
    • @ sam我无法注意或在环境问题上回答,但在检查某个复选框时,您的代码运行正常。所以是的,babel 可能没有加载,但这完全不确定。在任何情况下,您都可以简单地跳过 name 的第二个声明。这也是 MDN 示例的原因。当您覆盖给定的默认值时,在子类中重新声明变量是有道理的。例如。父类中的name='John' 可能是子类中的name='Peter'
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-09-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-29
    • 1970-01-01
    • 2016-05-05
    相关资源
    最近更新 更多