JS ES中的多态性不是错误吗



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

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

例如,我们有基类视图

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);

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

  • 选项卡视图构造函数
  • View contrustor(由 super() 调用)
  • TabView
  • init() (View 构造函数从 TabView 调用覆盖初始化方法)

选项卡视图的预期值

  • _viewModel:"模型">
  • _title:"测试">

以下示例值为TabView

  • _viewModel:"模型">
  • _title:"未定义">

当我调试此示例时,看起来当从View调用init()方法时,this引用View类而不是TabView。该值保存在View实例中,TabView字段仍为"未定义"。当我从类的顶部删除_title字段时TabView一切都可以按我的意愿工作。最新版本的Firefox和Microsoft Edge的结果相同。

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

当我调试此示例时,看起来当从View调用init()方法时,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

这意味着构造函数从TabView调用init,而又调用 setter 进行title

最终undefined_title的原因是类字段的规范(在撰写本文时,阶段 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);

最新更新