new
将调用
Construct
,它将依次调用相关函数的内部
[[Construct]]
. 我只讨论这里的[ [构造] ],而不关心代理,例如它有自定义行为,因为这与主题无关。
在标准场景中(否
extends
),在步骤5.a中,[[Construct]]调用
OrdinaryCreateFromConstructor
,其返回值将用作
this
(见
OrdinaryCallBindThis
,其中它用作参数)。请注意,OrdinaryCallEvaluateBody在后面的步骤中出现—在对构造函数求值之前创建对象。为了
new f
,基本上是
Object.create(f.prototype)
. 一般来说
Object.create(newTarget.prototype)
. 这是相同的
class
还有ES5方式。原型机显然也安装在那里。
这种困惑可能源于
延伸
正在使用。在这种情况下,[[ConstructorKind]不是“base”(参见第15步
ClassDefinitionEvaluation
),因此在[Construct]]中,步骤5.a不再应用,OrdinaryCallBindThis也不再调用。这里的重要部分发生在
super call
. 长话短说,它用超级结构和当前新目标调用Construct,并将结果绑定为
这
. 因此,如你所知,任何
这
在超级调用导致错误之前。因此,“new object”是在super调用中创建的(请注意,所讨论的再次应用于构造调用-如果super构造函数没有扩展任何内容,则为非派生情况,否则为这个情况-唯一的区别是newTarget)。
要详细说明newTarget转发,下面是一个示例,说明其行为:
class A { constructor() { console.log(`newTarget: ${new.target.name}`); } }
class B extends A { constructor(){ super(); } }
console.log(
`B.prototype's prototype: ${Object.getPrototypeOf(B.prototype).constructor.name}.prototype`
);
console.log("Performing `new A();`:");
new A();
console.log("Performing `new B();`:");
new B();
由于[[Construct]]调用orderycreatefromconstructor,newTarget作为参数,该参数始终被转发,因此使用的原型将是最后正确的原型(在上面的示例中,
B.prototype
,并注意到这反过来
A.prototype
作为原型,aka
Object.getPrototypeOf(B.prototype) === A.prototype
). 最好看看所有相关的部分(super call、Construct、[[Construct]]和OrdinaryCreateFromConstructor),看看它们是如何获得/设置或传递newTarget的。这里还要注意,对PrepareForOrdinaryCall的调用也得到了newTarget,并将其设置在相关超结构调用的FunctionEnvironment中,这样额外的链式超调用也将获得正确的目标(对于从依次从某物扩展的某物扩展的情况)。
最后但最不重要的是,构造函数可以使用
return
生产任何他们想要的东西。这通常导致在前面描述的步骤中创建的对象被简单地丢弃。但是,您可以执行以下操作:
const obj = {};
class T extends Number {
constructor() {
return obj;
}
}
let awkward = new T();
在这种非常尴尬的情况下,没有必要
super
,但也没有错误,因为构造函数只返回一些以前生成的对象。在这里,至少从我所看到的来看,当使用
new T()
.
还有一个副作用。如果您从一个构造函数进行扩展,该构造函数返回一些自制的对象、newTarget的转发以及所有没有效果的内容,扩展类的原型将丢失:
class A {
constructor() {
// The created object still has the function here.
// Note that in all normal cases, this should not
// be in the constructor of A, it's just to show
// what is happening.
this.someFunc();
//rip someFunc, welcome someNewFunc
return {
someNewFunc() { console.log("I'm new!"); }
};
}
}
class B extends A {
constructor() {
super();
//We get the new function here, after the call to super
this.someNewFunc();
}
someFunc() { console.log("something"); }
}
console.log("Performing `new B();`:");
let obj = new B();
console.log("Attempting to call `someFunc` on the created obj:");
obj.someFunc(); // This will throw an error.
注:我也是第一次在说明书中读到很多,所以可能会有一些错误。我自己的兴趣是找出扩展内置组件是如何工作的(源于一段时间前的另一场争论)。要理解这一点,在上述内容之后,只需要最后一件事:我们注意到
Number constructor
,检查是否“如果NewTarget未定义[…]”,否则将正确调用OrdinalCreateFromConstructor和NewTarget,同时添加内部的[NumberValue]]插槽,然后在下一步中设置它。
编辑以尝试回答评论中的问题:
我想你还在看
班
和ES5一样是两个独立的东西。
班
几乎完全是句法上的糖,正如在对这个问题的评论中已经提到的。类只不过是一个函数,类似于“旧的ES5方式”。
对于你的第一个问题,你提到的“方法”是一个函数,它将以ES5的方式使用(变量将保存什么,
class A extends Number {}; console.log(typeof A === "function" && Object.getPrototypeOf(A) === Number);
). 原型已经设置好,以实现您前面提到的“继承静态属性”。静态属性只不过是构造函数上的属性(如果您曾经使用过ES5方法)。
[[HomeObject]]用于访问
超级的
,如中所述
table 27
. 如果你看看相关的电话是怎么做的(参见
table 27
,
GetSuperBase
),你会注意到它,本质上,只是做了“[[HomeObject]].[GetPrototypeOf]()”。这将是超类原型,它应该是,所以
super.someProtoMethod
在超类的原型上工作。
对于第二个问题,我认为最好通过一个例子:
class A { constructor() { this.aProp = "aProp"; } }
class B extends A { constructor() { super(); this.bProp = "bProp"; }
new B();
我试着列出有趣的步骤,按顺序执行,当
new B();
正在评估:
-
新的
calls Construct,由于没有当前的newTarget,它调用了
B
新目标设置为
乙
.
-
[[Construct]]遇到一种不是“base”的类型,因此不会创建任何对象
-
PrepareForOrdinaryCall,用于执行构造函数,生成一个新的执行上下文,以及一个新的FunctionEnvironment(其中,[[NewTarget]]将设置为NewTarget!),并使其成为正在运行的执行上下文。
-
orderyCallBind也不执行此操作,并且
这
保持未初始化状态
-
OrdinaryCallEvaluateBody现在将开始执行
乙
-
遇到并执行超级调用:
-
GetNewTarget()从先前设置的FunctionEnvironment中检索[[NewTarget]]
-
构造是在超结构上调用的,而检索到的新目标
-
它调用超级结构的[[构造]],使用newTarget
-
超级构造函数具有“base”类,因此它执行orderyCreateFromConstructor,但具有newTarget集。这是现在的本质
Object.create(B.prototype)
,请再次注意
Object.getPrototypeOf(B.prototype)==原型
,已经在函数上设置了
乙
,来自班级建设。
-
与上面类似,正在创建一个新的执行上下文,这次,orderyCallBindThis也完成了。超级构造函数将执行,生成一些对象,再次弹出执行上下文。注意应该
A
反过来又扩展了一些其他的东西,newTarget又被正确地设置在任何地方,所以它会越来越深。
-
super从constructor(超级构造函数生成的对象)中获取结果
B、 原型
作为prototype,如果没有异常发生(如所讨论的,构造函数返回一些其他值,或者prototype被手动更改),并将其设置为
这
在当前环境中,用于执行
乙
(另一个已经被弹出)。
-
的构造函数的执行
乙
继续,用
这
现在初始化。它是一个物体
B、 原型
作为原型,反过来
A、 原型
作为原型
一个
构造函数已经被调用(同样,如果没有发生异常情况的话),所以
this.aProp
已经存在。的构造器
乙
然后将添加
bProp
,而该对象是
新B();
.