useDefineForClassFields
与类(class)有关,当 useDefineForClassFields
为 true 时,TypeScript 编译器会生成符合 ECMAScript 标准的类字段。useDefineForClassFields
有利于我们平滑地升级 TypeScript 。
es6 借鉴了传统的面向对象语言(比如 C++ 和 Java),引入了类的概念,可以通过 class 关键字定义类。class 其实是 function 的语法糖。例如下面的代码:
class Point {
x: number
y: number
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
与下面 ES5 的构造函数代码本质是一样的:
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toString = function () {
return '(' + this.x + ', ' + this.y + ')';
};
TypeScript 在 TC39 批准之前很多年就引入了类字段(class),TypeScript 中的类与标准中的类虽然语法相同,但是运行时行为却不一样,TypeScript 为了向标准看起,同时帮助开发者平滑地升级,就定义了 useDefineForClassFields
字段。
当 tsconfig 中 target
为 ES2022
及以上版本时(包括 ESNext
)时,useDefineForClassFields
默认为 true 。否则,默认为 false 。
下面看看一段 TypeScript 代码:
class C {
foo = 100;
bar: string;
}
const c = new C()
当 tsconfig 中 target
配置为 es6
,没有启用 useDefineForClassFields
时,编译结果如下:
class C {
constructor() {
this.foo = 100;
}
}
const c = new C();
当 tsconfig 中 target
配置为 es6
,启用 useDefineForClassFields
时,编译结果如下:
class C {
constructor() {
Object.defineProperty(this, "foo", {
enumerable: true,
configurable: true,
writable: true,
value: 100
});
Object.defineProperty(this, "bar", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
}
}
const c = new C();
可以看到变化主要由如下两点:
-
字段声明的方式从
=
赋值的方式变更成了Object.defineProperty
-
所有的字段声明都会生效,即使它没有指定默认值
默认 =
赋值的方式是 [[Set]]
语义,因为 this.foo = 100
这个操作会隐式地调用上下文中 foo
的 setter
。相应地 Object.defineProperty
的方式是 [[Define]]
语义。
当启用 useDefineForClassFields
时,类内部属性的定义由 [[Set]]
语义变为 [[Define]]
。
[[Set]]
语义是一种相对直接、简单的属性值设定方式。只是提供一种简单直接的对象属性赋值方式,用于快速更新或创建对象属性的值,而不涉及对属性其他复杂特性(如可枚举性、可配置性等)的精细设置。
[[Define]]
语义会比 [[Set]]
语义更加严格、精准地把控属性各方面行为特性。比如规定属性的可枚举性(enumerable
)、可配置性(configuarable
)、可写性(writable
)等。能用于属性访问控制、数据封装等复杂场景。
以 Vue.js 为例,在定义响应式数据时,虽然 Vue.js 内部对数据属性进行了很多复杂的处理,但底层原理是借助 [[Define]]
语义,通过精确地定义属性的特性来实现数据的响应式更新。
class C {
constructor() {
// 定义 foo 属性的可枚举性、可配置性、可写性
Object.defineProperty(this, "foo", {
enumerable: true,
configurable: true,
writable: true,
value: 100
});
Object.defineProperty(this, "bar", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
}
}
const c = new C();
总结
代码要向标准看起,如无特殊情况,尽量将 useDefineForClassFields
设置为 true 。
当启用 useDefineForClassFields
时,类内部属性的定义由 [[Set]]
语义变为 [[Define]]
。[[Define]]
语义会比 [[Set]]
语义更加严格、精准地把控属性各方面行为特性。