我有一个班级A
,还有一个继承自它的班级B
。
class A {
constructor(){
this.init();
}
init(){}
}
class B extends A {
private myMember = {value:1};
constructor(){
super();
}
init(){
console.log(this.myMember.value);
}
}
const x = new B();
当我运行此代码时,出现以下错误:
Uncaught TypeError: Cannot read property 'value' of undefined
如何避免此错误?
我很清楚,JavaScript代码会在创建init
之前调用myMember
方法,但应该有一些练习/模式才能使其正常工作。
答案 0 :(得分:14)
这就是为什么在某些语言(咳嗽C#)代码分析工具标记构造函数内虚拟成员的使用。
在Typescript字段中,在调用基础构造函数之后,在构造函数中进行初始化。字段初始化写在字段附近的事实只是语法糖。如果我们查看生成的代码,问题就会变得清晰:
function B() {
var _this = _super.call(this) || this; // base call here, field has not been set, init will be called
_this.myMember = { value: 1 }; // field init here
return _this;
}
您应该考虑一种解决方案,其中init从实例外部调用,而不是在构造函数中调用:
class A {
constructor(){
}
init(){}
}
class B extends A {
private myMember = {value:1};
constructor(){
super();
}
init(){
console.log(this.myMember.value);
}
}
const x = new B();
x.init();
或者你可以为你的构造函数添加一个额外的参数来指定是否调用init
而不是在派生类中调用它。
class A {
constructor()
constructor(doInit: boolean)
constructor(doInit?: boolean){
if(doInit || true)this.init();
}
init(){}
}
class B extends A {
private myMember = {value:1};
constructor()
constructor(doInit: boolean)
constructor(doInit?: boolean){
super(false);
if(doInit || true)this.init();
}
init(){
console.log(this.myMember.value);
}
}
const x = new B();
或setTimeout
的非常非常脏的解决方案,它将推迟初始化直到当前帧完成。这将让父构造函数调用完成,但构造函数调用之间会有一个临时的时间,并且当对象未被init
编辑时超时到期时
class A {
constructor(){
setTimeout(()=> this.init(), 1);
}
init(){}
}
class B extends A {
private myMember = {value:1};
constructor(){
super();
}
init(){
console.log(this.myMember.value);
}
}
const x = new B();
// x is not yet inited ! but will be soon
答案 1 :(得分:5)
因为在父构造函数中访问myMember
属性(在init()
调用期间调用super()
),所以如果没有遇到竞争条件,就无法在子构造函数中定义它。
有几种替代方法。
init
hook init
被认为是不应该在类构造函数中调用的钩子。相反,它被明确地调用:
new B();
B.init();
或者它是由框架隐式调用的,作为应用程序生命周期的一部分。
如果属性应该是常量,则它可以是静态属性。
这是最有效的方法,因为这是静态成员的用途,但语法可能不那么有吸引力,因为如果在子类中正确引用静态属性,则需要使用this.constructor
而不是类名:
class B extends A {
static readonly myMember = { value: 1 };
init() {
console.log((this.constructor as typeof B).myMember.value);
}
}
可以使用get
/ set
语法在类原型上定义属性描述符。如果一个属性应该是原始常量,那么它可以只是一个吸气剂:
class B extends A {
get myMember() {
return 1;
}
init() {
console.log(this.myMember);
}
}
如果属性不是常数或原始的话,那就变得更加黑客了:
class B extends A {
private _myMember?: { value: number };
get myMember() {
if (!('_myMember' in this)) {
this._myMember = { value: 1 };
}
return this._myMember!;
}
set myMember(v) {
this._myMember = v;
}
init() {
console.log(this.myMember.value);
}
}
可以在首先访问的位置初始化属性。如果在init
方法中发生这种情况,this
类构造函数之前可以访问B
,则应该在那里进行:
class B extends A {
private myMember?: { value: number };
init() {
this.myMember = { value: 1 };
console.log(this.myMember.value);
}
}
init
方法可能会异步。初始化状态应该是可跟踪的,因此该类应该实现一些API,例如许基于:
class A {
initialization = Promise.resolve();
constructor(){
this.init();
}
init(){}
}
class B extends A {
private myMember = {value:1};
init(){
this.initialization = this.initialization.then(() => {
console.log(this.myMember.value);
});
}
}
const x = new B();
x.initialization.then(() => {
// class is initialized
})
对于这种特殊情况,这种方法可能被认为是反模式,因为初始化例程本质上是同步的,但它可能适用于异步初始化例程。
由于ES6类在this
之前对super
的使用有限制,因此可以将子类置于函数中以避开此限制:
interface B extends A {}
interface BPrivate extends B {
myMember: { value: number };
}
interface BStatic extends A {
new(): B;
}
const B = <BStatic><Function>function B(this: BPrivate) {
this.myMember = { value: 1 };
return A.call(this);
}
B.prototype.init = function () {
console.log(this.myMember.value);
}
这很少是一个很好的选择,因为desugared类应该在TypeScript中另外输入。这也不适用于本机父类(TypeScript es6
和esnext
目标)。
答案 2 :(得分:3)
您可以采用的一种方法是使用 myMember 的getter / setter并管理getter中的默认值。这可以防止未定义的问题,并允许您保持几乎完全相同的结构。像这样:
class A {
constructor(){
this.init();
}
init(){}
}
class B extends A {
private _myMember;
constructor(){
super();
}
init(){
console.log(this.myMember.value);
}
get myMember() {
return this._myMember || { value: 1 };
}
set myMember(val) {
this._myMember = val;
}
}
const x = new B();
答案 3 :(得分:2)
试试这个:
class A {
constructor() {
this.init();
}
init() { }
}
class B extends A {
private myMember = { 'value': 1 };
constructor() {
super();
}
init() {
this.myMember = { 'value': 1 };
console.log(this.myMember.value);
}
}
const x = new B();
答案 4 :(得分:2)
超级必须是第一个命令。记住打字稿是更多&#34; javascript与文件类型&#34;而不是语言本身。
如果您查看已编译的代码.js,则可以清楚地看到:
class A {
constructor() {
this.init();
}
init() {
}
}
class B extends A {
constructor() {
super();
this.myMember = { value: 1 };
}
init() {
console.log(this.myMember.value);
}
}
const x = new B();
答案 5 :(得分:2)
您是否必须在A类中调用init?
工作正常,但我不知道你是否有不同的要求:
class A {
constructor(){}
init(){}
}
class B extends A {
private myMember = {value:1};
constructor(){
super();
this.init();
}
init(){
console.log(this.myMember.value);
}
}
const x = new B();
答案 6 :(得分:0)
像这样:
class A
{
myMember;
constructor() {
}
show() {
alert(this.myMember.value);
}
}
class B extends A {
public myMember = {value:1};
constructor() {
super();
}
}
const test = new B;
test.show();