多态性在 JS ES 中不是错误的吗
Isn't polymorphism works wrong in JS ES
我目前正在开发一个浏览器扩展来管理打开的选项卡,我注意到在 JS ES 中,当我在 class 顶部声明 class 字段时,多态性工作起来有点奇怪。
假设我们想在对象初始化中使用多态性。
例如我们有基础 class View:
class View {
_viewModel;
constructor(viewModel) {
this._viewModel = viewModel;
this.init();
}
init() { }
}
并导出classTabView:
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 构造函数
- 查看构造函数(由 super() 调用)
- TabView init()(视图构造函数从 TabView 调用覆盖的 init 方法)
TabView 的预期值:
- _viewModel:“模型”
- _title: "测试"
此示例的 TabView 值:
- _viewModel:“模型”
- _title:“未定义”
当我调试这个例子时,看起来当从 View
调用 init()
方法时 this
指的是 View
class 而不是 TabView
。该值保存在 View
实例中,并且 TabView
字段仍然是 'undefined'。当我从 TabView
class 的顶部删除 _title
字段时,一切都按我的意愿进行。最新版本的 Firefox 和 Microsoft Edge 的结果相同。
我喜欢将 class 字段写在顶部,所以我想问一下它是否是 JS ES 的正确行为,或者它可能是一个错误,可能会在未来的 ECMA 脚本版本中更正?
When I debug this example, it looks like when init()
method is invoked from View
then this
refers to View
class instead of TabView
. The value was saved in View
instance, and TabView
field was still '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
,后者又为 title
.
调用 setter
_title
是 undefined
的原因最终是 the specification for class fields(在撰写本文时是第 3 阶段提案)。这是相关部分:
Fields without initializers are set to undefined
Both public and private field declarations create a field in the instance, whether or not there's an initializer present. If there's no initializer, the field is set to undefined
. This differs a bit from certain transpiler implementations, which would just entirely ignore a field declaration which has no initializer.
因为_title
没有在TabView
内初始化,规范定义在构造器执行完之后它的值应该是undefined
。
这里有几个选项,但是如果你想将 _title
声明为 class 字段, 和 有不同的值,你有给字段一个值作为 TabView
实例化的一部分,而不是作为其 parent(或 grandparents 等)的一部分。
字段初始化程序
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);
我目前正在开发一个浏览器扩展来管理打开的选项卡,我注意到在 JS ES 中,当我在 class 顶部声明 class 字段时,多态性工作起来有点奇怪。
假设我们想在对象初始化中使用多态性。
例如我们有基础 class View:
class View {
_viewModel;
constructor(viewModel) {
this._viewModel = viewModel;
this.init();
}
init() { }
}
并导出classTabView:
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 构造函数
- 查看构造函数(由 super() 调用)
- TabView init()(视图构造函数从 TabView 调用覆盖的 init 方法)
TabView 的预期值:
- _viewModel:“模型”
- _title: "测试"
此示例的 TabView 值:
- _viewModel:“模型”
- _title:“未定义”
当我调试这个例子时,看起来当从 View
调用 init()
方法时 this
指的是 View
class 而不是 TabView
。该值保存在 View
实例中,并且 TabView
字段仍然是 'undefined'。当我从 TabView
class 的顶部删除 _title
字段时,一切都按我的意愿进行。最新版本的 Firefox 和 Microsoft Edge 的结果相同。
我喜欢将 class 字段写在顶部,所以我想问一下它是否是 JS ES 的正确行为,或者它可能是一个错误,可能会在未来的 ECMA 脚本版本中更正?
When I debug this example, it looks like when
init()
method is invoked fromView
thenthis
refers toView
class instead ofTabView
. The value was saved inView
instance, andTabView
field was still'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
,后者又为 title
.
_title
是 undefined
的原因最终是 the specification for class fields(在撰写本文时是第 3 阶段提案)。这是相关部分:
Fields without initializers are set to
undefined
Both public and private field declarations create a field in the instance, whether or not there's an initializer present. If there's no initializer, the field is set to
undefined
. This differs a bit from certain transpiler implementations, which would just entirely ignore a field declaration which has no initializer.
因为_title
没有在TabView
内初始化,规范定义在构造器执行完之后它的值应该是undefined
。
这里有几个选项,但是如果你想将 _title
声明为 class 字段, 和 有不同的值,你有给字段一个值作为 TabView
实例化的一部分,而不是作为其 parent(或 grandparents 等)的一部分。
字段初始化程序
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);