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]] 语义更加严格、精准地把控属性各方面行为特性。