Rya*_*ugh 19 javascript class typescript ecmascript-6
我写了一些代码:
class Base {
// Default value
myColor = 'blue';
constructor() {
console.log(this.myColor);
}
}
class Derived extends Base {
myColor = 'red';
}
// Prints "blue", expected "red"
const x = new Derived();
Run Code Online (Sandbox Code Playgroud)
我期望我的派生类字段初始化程序在基类构造函数之前运行.相反,派生类myColor在基类构造函数运行之前不会更改属性,因此我在构造函数中观察到错误的值.
这是一个错误吗?怎么了?为什么会这样?我该怎么做呢?
Rya*_*ugh 30
首先,这不是TypeScript,Babel或JS运行时中的错误.
您可能有的第一个后续行动是"为什么不正确做到这一点!?!?".我们来看一下TypeScript发射的具体情况.实际答案取决于我们为其发布类代码的ECMAScript版本.
我们来看看TypeScript为ES3或ES5发出的代码.为了便于阅读,我简化了+注释了一下:
var Base = (function () {
function Base() {
// BASE CLASS PROPERTY INITIALIZERS
this.myColor = 'blue';
console.log(this.myColor);
}
return Base;
}());
var Derived = (function (_super) {
__extends(Derived, _super);
function Derived() {
// RUN THE BASE CLASS CTOR
_super();
// DERIVED CLASS PROPERTY INITIALIZERS
this.myColor = 'red';
// Code in the derived class ctor body would appear here
}
return Derived;
}(Base));
Run Code Online (Sandbox Code Playgroud)
基类的发射是无可争议的 - 字段被初始化,然后构造函数体运行.你当然不希望相反 - 在运行构造函数体之前初始化字段意味着你不能在构造函数之后看到字段值,这不是任何人想要的.
派生类是否正确发出?
许多人会争辩派生类的发射应该如下所示:
// DERIVED CLASS PROPERTY INITIALIZERS
this.myColor = 'red';
// RUN THE BASE CLASS CTOR
_super();
Run Code Online (Sandbox Code Playgroud)
由于各种原因,这是错误的:
'red'用于myColor将由基类值立即被覆盖"蓝"在最后一点,请考虑以下代码:
class Base {
thing = 'ok';
getThing() { return this.thing; }
}
class Derived extends Base {
something = this.getThing();
}
Run Code Online (Sandbox Code Playgroud)
如果派生类初始值设定项在基类初始值设定项之前运行,Derived#something那么undefined它应该是明确的'ok'.
许多其他人会争辩说,应该做一个模糊的其他东西,以便Base知道Derived有一个字段初始化器.
您可以编写依赖于了解要运行的整个代码范围的示例解决方案.但是TypeScript/Babel/etc不能保证这个存在.例如,Base可以在一个单独的文件中,我们无法看到它的实现.
如果你还不知道这个,那就该学习了:类不是TypeScript特性.它们是ES6的一部分,并定义了语义.但是ES6类不支持字段初始化器,因此它们转换为与ES6兼容的代码.它看起来像这样:
class Base {
constructor() {
// Default value
this.myColor = 'blue';
console.log(this.myColor);
}
}
class Derived extends Base {
constructor() {
super(...arguments);
this.myColor = 'red';
}
}
Run Code Online (Sandbox Code Playgroud)
代替
super(...arguments);
this.myColor = 'red';
Run Code Online (Sandbox Code Playgroud)
我们应该这样吗?
this.myColor = 'red';
super(...arguments);
Run Code Online (Sandbox Code Playgroud)
不,因为它不起作用.在派生类中this调用之前引用是非法的super.它根本无法以这种方式工作.
控制JavaScript的TC39委员会正在研究将字段初始化程序添加到该语言的未来版本中.
您可以在GitHub上阅读它或阅读有关初始化顺序的特定问题.
所有OOP语言都有一个通用指南,有些是明确强制执行的,有些是按惯例隐式执行的:
不要从构造函数中调用虚方法
例子:
在JavaScript中,我们必须稍微扩展此规则
不要观察构造函数中的虚拟行为
和
类属性初始化计为虚拟
标准解决方案是将字段初始化转换为构造函数参数:
class Base {
myColor: string;
constructor(color: string = "blue") {
this.myColor = color;
console.log(this.myColor);
}
}
class Derived extends Base {
constructor() {
super("red");
}
}
// Prints "red" as expected
const x = new Derived();
Run Code Online (Sandbox Code Playgroud)
您也可以使用init模式,但是您需要小心不要观察它的虚拟行为,并且不要在派生init方法中执行需要完全初始化基类的事情:
class Base {
myColor: string;
constructor() {
this.init();
console.log(this.myColor);
}
init() {
this.myColor = "blue";
}
}
class Derived extends Base {
init() {
super.init();
this.myColor = "red";
}
}
// Prints "red" as expected
const x = new Derived();
Run Code Online (Sandbox Code Playgroud)
通过执行意想不到的操作,这是破坏常见类扩展用例的不良行为。这是支持您的用例的初始化顺序,我认为更好:
Base property initializers
Derived property initializers
Base constructor
Derived constructor
Run Code Online (Sandbox Code Playgroud)
- 打字稿编译器当前在构造函数中发出属性初始化
这里的解决方案是将属性初始化与构造函数的调用分开。C# 就是这样做的,尽管它在派生属性之后初始化其基本属性,这也是违反直觉的。这可以通过发出辅助类来实现,以便派生类可以按任意顺序初始化基类。
class _Base {
ctor() {
console.log('base ctor color: ', this.myColor);
}
initProps() {
this.myColor = 'blue';
}
}
class _Derived extends _Base {
constructor() {
super();
}
ctor() {
super.ctor();
console.log('derived ctor color: ', this.myColor);
}
initProps() {
super.initProps();
this.myColor = 'red';
}
}
class Base {
constructor() {
const _class = new _Base();
_class.initProps();
_class.ctor();
return _class;
}
}
class Derived {
constructor() {
const _class = new _Derived();
_class.initProps();
_class.ctor();
return _class;
}
}
// Prints:
// "base ctor color: red"
// "derived ctor color: red"
const d = new Derived();
Run Code Online (Sandbox Code Playgroud)
- 基构造函数不会因为我们使用派生类属性而中断吗?
基构造函数中中断的任何逻辑都可以移至将在派生类中重写的方法。由于派生方法是在调用基本构造函数之前初始化的,因此这可以正常工作。例子:
class Base {
protected numThings = 5;
constructor() {
console.log('math result: ', this.doMath())
}
protected doMath() {
return 10/this.numThings;
}
}
class Derived extends Base {
// Overrides. Would cause divide by 0 in base if we weren't overriding doMath
protected numThings = 0;
protected doMath() {
return 100 + this.numThings;
}
}
// Should print "math result: 100"
const x = new Derived();
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
3975 次 |
| 最近记录: |