JS ES 中的多态性是不是有问题 没有初始值设定项的字段设置为 undefined字段初始化器在构造函数中初始化值调用初始化字段的方法删除字段声明

问题描述

我目前正在开发一个浏览器扩展来管理打开的选项卡,我注意到在 JS ES 中,当我在类的顶部声明类字段时,多态性的工作有点奇怪。

假设我们想在对象初始化中使用多态。

例如我们有基类 View

class View {
    _viewmodel;

    constructor(viewmodel) {
        this._viewmodel = viewmodel;
        this.init();
    }

    init() { }
}

和派生类TabView

class TabView extends View {
    _title;

    constructor(viewmodel) {
        super(viewmodel);
    }

    init() {
        this.title = "test";
    }

    get title() {
        return this._title;
    }
    set title(value) {
        this._title = value;
    }
}

现在让我们尝试调用索引文件中的简单脚本来调试这个例子。

const tabView = new TabView("model");
console.log(tabView.title);

此示例的调用堆栈看起来正确(从上到下读取):

TabView 的预期值

  • _viewmodel:“模型”
  • _title: "测试"

TabView 的示例值

  • _viewmodel:“模型”
  • _title: "未定义"

当我调试这个例子时,看起来当从 init() 调用 View 方法时,this 指的是 View 类而不是 TabView。该值已保存在 View 实例中,而 TabView 字段仍为“未定义”。当我从 _title 类的顶部删除 TabView 字段时,一切都如我所愿。最新版本的 Firefox 和 Microsoft Edge 的结果相同。

我喜欢将类字段写在顶部,所以我想问一下它是否是 JS ES 的正确行为,或者它是否是一个可能会在未来版本的 ECMA 脚本中更正的错误

解决方法

当我调试这个例子时,看起来当从 init() 调用 View 方法时,this 指的是 View 类而不是 TabView。该值已保存在 View 实例中,而 TabView 字段仍为 'undefined'

看看这段代码:

class View {
    _viewModel;

    constructor(viewModel) {
        this._viewModel = viewModel;
        this.init();
    }

    init() { console.log("View init"); }
}

class TabView extends View {
    _title;

    constructor(viewModel) {
        super(viewModel);
    }

    init() {
        console.log("TabView init");
        this.title = "test";
    }

    get title() {
        console.log("get title");
        return this._title;
    }
    
    set title(value) {
        console.log("set title");
        this._title = value;
    }
}

const tabView = new TabView("model");
console.log(tabView.title);

此日志

TabView init
set title
get title

这意味着构造函数从 init 调用 TabView,后者又调用 title 的 setter。

_title 最终是 undefined 的原因是 the specification for class fields(在撰写本文时为第 3 阶段提案)。这是相关部分:

没有初始值设定项的字段设置为 undefined

公共和私有字段声明都会在实例中创建一个字段,无论是否存在初始值设定项。如果没有初始值设定项,则该字段设置为 undefined。这与某些转译器实现略有不同,后者会完全忽略没有初始化器的字段声明。

由于 _title 未在 TabView 内初始化,因此规范定义在构造函数完成执行后其值应为 undefined

您在这里有几个选项,但如果您想将 _title 声明为类字段并且为其指定不同的值,则必须为该字段指定一个值作为TabView 实例化,而不是作为其父级(或祖父级等)的一部分。

字段初始化器

class TabView extends View {
    _title = "test"; //give value to the field directly

    constructor(viewModel) {
        super(viewModel);
    }
    /* ... */
}

class View {
    _viewModel;

    constructor(viewModel) {
        this._viewModel = viewModel;
        this.init();
    }

    init() { }
}

class TabView extends View {
    _title = "test"; //give value to the field directly

    constructor(viewModel) {
        super(viewModel);
    }

    get title() {
        return this._title;
    }
    set title(value) {
        this._title = value;
    }
}

const tabView = new TabView("model");
console.log(tabView.title);

在构造函数中初始化值

class TabView extends View {
    _title;

    constructor(viewModel) {
        super(viewModel);
        this._title = "test"; //give value to `_title` in the constructor
    }
    /* ... */
}

class View {
    _viewModel;

    constructor(viewModel) {
        this._viewModel = viewModel;
        this.init();
    }

    init() { }
}

class TabView extends View {
    _title;

    constructor(viewModel) {
        super(viewModel);
        this._title = "test"; //give value in the constructor
    }

    get title() {
        return this._title;
    }
    set title(value) {
        this._title = value;
    }
}

const tabView = new TabView("model");
console.log(tabView.title);

调用初始化字段的方法

class TabView extends View {
    _title;

    constructor(viewModel) {
        super(viewModel);
        this.init(); //call `init` which will give value to the `_title` field
    }
    
    init() {
      this.title = "test";
    }
    /* ... */
}

class View {
    _viewModel;

    constructor(viewModel) {
        this._viewModel = viewModel;
        this.init();
    }

    init() { }
}

class TabView extends View {
    _title;

    constructor(viewModel) {
        super(viewModel);
        this.init(); //call `init` which will give value to the `_title` field
    }
    
    init() {
      this.title = "test";
    }

    get title() {
        return this._title;
    }
    set title(value) {
        this._title = value;
    }
}

const tabView = new TabView("model");
console.log(tabView.title);

删除字段声明

class TabView extends View {
    //no declaration here

    constructor(viewModel) {
        super(viewModel);
    }
    /* ... */
}

class View {
    _viewModel;

    constructor(viewModel) {
        this._viewModel = viewModel;
        this.init();
    }

    init() { console.log("View init"); }
}

class TabView extends View {
    //no declaration here

    constructor(viewModel) {
        super(viewModel);
    }

    init() {
        this.title = "test";
    }

    get title() {
        return this._title;
    }
    
    set title(value) {
        this._title = value;
    }
}

const tabView = new TabView("model");
console.log(tabView.title);

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...