通过复写构造器直接调用的方法内的赋值变量会被重置为undefined,我不太理解,这是Cocos的Bug吗?遇到这种问题实在是让人抓狂

我不晓得是不是Cocos对原生的JS上下文环境做了什么,以下代码跟预期输出不一样:
Creator 版本: 3.7
目标平台: PC chrome

import { _decorator, Component, Node } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('Main')
export class Main extends Component {
    start() {
        new Data();
    }

    update(deltaTime: number) {
        
    }
}

abstract class AbstractBase {
    public aa: number;
    constructor() {
        this.aa = 2;
        this.create();
    }
    abstract create()
}

class Data extends AbstractBase {
    public bb: number;
    create() {
        this.bb = 222;
        setInterval(() => console.log(this), 1000);
    }
}

预期应该是bb是222,但是奇怪的事情发生了结果是undefined,遇到这种问题实在是让人抓狂

那你可以放进其它 JS 环境运行一下就知道了,NodeJS 下也是这样,因为 JS 就是这样的:

// base prop -> base ctor -> sub prop -> sub ctor

上面是 JS 对象构造时的流程,你的 create 函数执行在 base ctor 这一步,自然被 sub prop 覆盖了:

abstract class AbstractBase {
    public aa: number;    // base prop
    constructor() {    // base ctor
        this.aa = 2;
        this.create();
    }
    abstract create()
}

class Data extends AbstractBase {
    public bb: number;    // sub prop
    create() {
        this.bb = 222;
    }
}

如果你之前这么写没问题,那么可能是之前的环境因为你没给它赋值而忽略了 sub prop 这个流程,但符合规范的实现是要将所有声明的没给赋值的字段初始化为 undefined 的,3.x 中可以手动关闭这个规范。

或者你可以这么写:

class Data extends AbstractBase {
    declare public bb: number;    // 注意这里的 declare
}

也可以让代码按照你想的进行。

2赞

public bb: number;
改为
public bb!: number;
试试看

我之前遇到过,因为实例化子类时构造函数的实际顺序是

  1. 基类成员定义
  2. 基类构造函数
  3. 子类成员定义
  4. 子类构造函数

在基类构造函数内给子类成员赋值会在下一步 子类成员定义 被覆盖,加 ! 则可以声明这个成员属性不需要构造定义(undefined)

1赞

我在nodejs中测试不是这样的

class AbstractBase {
    constructor() {    // base ctor
        this.aa = 2;
        this.create();
    }
    create() { }
}

class Data extends AbstractBase {
    create() {
        this.bb = 222;
        setInterval(() => console.log(this), 1000);
    }
}

new Data();

//output  Data { aa: 2, bb: 222 }

  • 应该是开启了编译选项allowDeclareFields导致的,
  • 我用了nodejs很久了,白鹭和layabox我都用过,都没有遇到类似问题。
    我想我遇到的问题,应该是不在预期中的。
  • 如果开启allowDeclareFields应该同时开启strictPropertyInitialization以提示错误.
  • 但是我建议默认关闭allowDeclareFields选项

这个方法应该可行

2.x上测试是没问题的,能输出Data { aa: 2, bb: 222 },这是因为什么设置不同导致的?

提过一个类似的。


我自己是这样设置的

@ 你什么身份我什么地位 建议后面版本默认关闭 allowDeclareFields 选项

执行顺序确实是按照 base prop -> base ctor -> sub prop -> sub ctor,这个是肯定的,
唯一差异是在public bb: number;这句在编译后会不会变成bb=undefined,
bb赋值为undefined后按照上面的顺序执行,就是undefined

关键这不符合预期

这个行为符合类字段行为的标准。

可以通过先编译这段代码到 tsc,再在 Node.js 中验证。

编译时需要勾选 useDefineForClassFields,因为这就是标准的行为,tsc 是因为历史原因没有默认开启:

TypeScript: TS Playground - An online editor for exploring TypeScript and JavaScript (typescriptlang.org)

image

你也可以直接写出对应的 js 代码,看看它在 Node.js 中的运行结果:

class AbstractBase {
    aa;    // base prop
    constructor() { this.aa = 2; this.create(); }
}

class Data extends AbstractBase {
    bb;    // sub prop
    create() { this.bb = 222; setInterval(() => console.log(this), 1000); }
}

new Data();

运行结果:

如果你不希望这种行为,如上面的回复所述,可以修改一些编译选项来实现。

收到,我们研究下先

  • 我测了下确实是undefined,但是我们写JS的时候通常不会去声明属性
  • 其他语言在和例子相同的写法的时候,不会出现undefined的情况(我只测了C#,太费时间),这应该是一种预期。
  • 我猜测ts的初始化顺序是一种对js妥协的产物base prop -> base ctor -> sub prop -> sub ctor.因为js你无法在super之前取到this.
1赞