我发现了js扩展的有趣行为,并且不理解它的原因
在从另一个值复制值的情况下,由于某些原因值将从父
class parent {
defaultValue = 1;
value = this.defaultValue;
}
new parent() // {defaultValue: 1, value: 1}
class child extends parent {
defaultValue = 2;
}
new child() // {defaultValue: 2, value: 1}
这对我来说真的不是很明显和不清楚
但是如果我用function甚至getter来代替它行为将会改变,我从child
class parent {
get defaultValue() { return 1; }
value = this.defaultValue;
}
new parent() // {defaultValue: 1, value: 1}
class child extends parent {
get defaultValue() { return 2; }
}
new child() // {defaultValue: 2, value: 2}
这里的主要问题是,为什么在第一种情况下,JS在父类中寻找值,但在第二种情况下,JS在子类中寻找值
有人能解释一下这种行为的原因吗?编辑详情请参阅t.t niese或Yury Tarabanko的回答
简短的答案似乎在下面
getter(也是function)和function将在原型中被重写,从而允许父类在子类更改时调用它们(实际上这是预期的)
而第一个带有简单赋值的示例只会在类创建时(构造函数或父类)被调用,并且它只会出现在当前类(不能被子类更改)和原型(可以被子类更改)的作用域
一个相关的问题是:如何在父类代码中访问被重写的父类函数
getter和setter是与类定义一起定义的函数,因此在parent
类的构造函数(以及它的实例类字段的初始化)中,您可以调用只存在于child
中的函数(这确实可能有点奇怪):
class parent {
value = this.test();
constructor() {
this.test()
}
}
class child extends parent {
test() {
console.log('test')
}
}
new child()
因此,在实例化完成之前,调用的函数(或getter/setter)已经用类定义定义好了。
另一方面,公共实例类字段在实例初始化阶段以特定顺序初始化/设置(所示代码可能仅适用于基于chrome的浏览器):class parent {
defaultValue = (() => {
console.log('parent:init defaultValue')
return 1;
})();
value = (() => {
console.log('parent:init value')
return this.defaultValue;
})();
constructor() {
console.log('parent constructor')
}
}
class child extends parent {
defaultValue = (() => {
console.log('child:init defaultValue')
return 2;
})();
constructor() {
console.log('child constructor before super()')
super()
console.log('child constructor after super()')
}
}
new child()
在第一个示例中,Child
中名为defaultValue
的公共实例字段的创建和初始化发生在Parent
中名为value
的公共实例字段的创建和初始化之后。
因此:即使Parent
中名为value
的公共实例字段的初始化器中的this
值指向正在构建的Child
的实例,但名为defaultValue
的子本地公共实例字段还不存在,因此原型链接着到Parent
实例上名为defaultValue
的属性,产生1
.
在后面的示例中,您有名为defaultValue
的getter函数。
以这种方式指定的Getter函数,即使它们的API故意看起来像公共实例字段,最终也会成为任何正在构建的实例的[[Prototype]]
上的函数。
实例的[[Prototype]]
对象是在类声明时创建的。总是在由实例构造触发的任何事情之前),作为类(或构造函数)的.prototype
属性;然后将对这些对象的引用复制到任何正在构建的实例的[[Prototype]]
中,作为对象构建的第一步(考虑Object.create(class.prototype))
.
)。因此,value
的Parent
公共实例初始化器中的this.defaultValue
解析为Child
正在构造的实例的[[Prototype]]
上的getter,即返回2
的函数。.
这是因为getter是在原型上定义的,而实例属性是在实例上定义的(顾名思义)
创建Child1
实例时它首先从Parent1
中定义属性然后得到defaultValue = 1
相反,当创建Child2
实例时,Child2.prototype
已经覆盖了defaultValue
属性。
class Parent1 {
defaultValue = 1;
value = this.defaultValue;
}
class Child1 extends Parent1 {
defaultValue = 2;
}
class Parent2 {
get defaultValue() { return 1; }
value = this.defaultValue;
}
class Child2 extends Parent2 {
get defaultValue() { return 2; }
}
console.log(Object.hasOwn(new Child1(), 'defaultValue'))
console.log(Object.hasOwn(new Child2(), 'defaultValue'))