Skip to content

Commit bbde468

Browse files
committed
docs(class): modify useDefineForClassFields, fixed wangdoc#113
1 parent 8d73b03 commit bbde468

File tree

2 files changed

+140
-59
lines changed

2 files changed

+140
-59
lines changed

docs/class.md

Lines changed: 134 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -954,65 +954,6 @@ class Test extends getGreeterBase() {
954954

955955
上面示例中,例一和例二的`extends`关键字后面都是构造函数,例三的`extends`关键字后面是一个表达式,执行后得到的也是一个构造函数。
956956

957-
对于那些只设置了类型、没有初值的顶层属性,有一个细节需要注意。
958-
959-
```typescript
960-
interface Animal {
961-
animalStuff: any;
962-
}
963-
964-
interface Dog extends Animal {
965-
dogStuff: any;
966-
}
967-
968-
class AnimalHouse {
969-
resident: Animal;
970-
971-
constructor(animal:Animal) {
972-
this.resident = animal;
973-
}
974-
}
975-
976-
class DogHouse extends AnimalHouse {
977-
resident: Dog;
978-
979-
constructor(dog:Dog) {
980-
super(dog);
981-
}
982-
}
983-
```
984-
985-
上面示例中,类`DogHouse`的顶层成员`resident`只设置了类型(`Dog`),没有设置初值。这段代码在不同的编译设置下,编译结果不一样。
986-
987-
如果编译设置的`target`设成大于等于`ES2022`,或者`useDefineForClassFields`设成`true`,那么下面代码的执行结果是不一样的。
988-
989-
```typescript
990-
const dog = {
991-
animalStuff: 'animal',
992-
dogStuff: 'dog'
993-
};
994-
995-
const dogHouse = new DogHouse(dog);
996-
997-
console.log(dogHouse.resident) // undefined
998-
```
999-
1000-
上面示例中,`DogHouse`实例的属性`resident`输出的是`undefined`,而不是预料的`dog`。原因在于 ES2022 标准的 Class Fields 部分,与早期的 TypeScript 实现不一致,导致子类的那些只设置类型、没有设置初值的顶层成员在基类中被赋值后,会在子类被重置为`undefined`,详细的解释参见《tsconfig.json》一章,以及官方 3.7 版本的[发布说明](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#the-usedefineforclassfields-flag-and-the-declare-property-modifier)
1001-
1002-
解决方法就是使用`declare`命令,去声明顶层成员的类型,告诉 TypeScript 这些成员的赋值由基类实现。
1003-
1004-
```typescript
1005-
class DogHouse extends AnimalHouse {
1006-
declare resident: Dog;
1007-
1008-
constructor(dog:Dog) {
1009-
super(dog);
1010-
}
1011-
}
1012-
```
1013-
1014-
上面示例中,`resident`属性的类型声明前面用了`declare`命令,这样就能确保在编译目标大于等于`ES2022`时(或者打开`useDefineForClassFields`时),代码行为正确。
1015-
1016957
## 可访问性修饰符
1017958

1018959
类的内部成员的外部可访问性,由三个可访问性修饰符(access modifiers)控制:`public``private``protected`
@@ -1278,6 +1219,140 @@ class A {
12781219
}
12791220
```
12801221

1222+
## 顶层属性的处理方法
1223+
1224+
对于类的顶层属性,TypeScript 早期的处理方法,与后来的 ES2022 标准不一致。这会导致某些代码的运行结果不一样。
1225+
1226+
类的顶层属性在 TypeScript 里面,有两种写法。
1227+
1228+
```typescript
1229+
class User {
1230+
// 写法一
1231+
age = 25;
1232+
1233+
// 写法二
1234+
constructor(private currentYear: number) {}
1235+
}
1236+
```
1237+
1238+
上面示例中,写法一是直接声明一个实例属性`age`,并初始化;写法二是顶层属性的简写形式,直接将构造方法的参数`currentYear`声明为实例属性。
1239+
1240+
TypeScript 早期的处理方法是,先在顶层声明属性,但不进行初始化,等到运行构造方法时,再完成所有初始化。
1241+
1242+
```typescript
1243+
class User {
1244+
age = 25;
1245+
}
1246+
1247+
// TypeScript 的早期处理方法
1248+
class User {
1249+
age: number;
1250+
1251+
constructor() {
1252+
this.age = 25;
1253+
}
1254+
}
1255+
```
1256+
1257+
上面示例中,TypeScript 早期会先声明顶层属性`age`,然后等到运行构造函数时,再将其初始化为`25`
1258+
1259+
ES2022 标准里面的处理方法是,先进行顶层属性的初始化,再运行构造方法。这在某些情况下,会使得同一段代码在 TypeScript 和 JavaScript 下运行结果不一致。
1260+
1261+
这种不一致一般发生在两种情况。第一种情况是,顶层属性的初始化依赖于其他实例属性。
1262+
1263+
```typescript
1264+
class User {
1265+
age = this.currentYear - 1998;
1266+
1267+
constructor(private currentYear: number) {
1268+
// 输出结果将不一致
1269+
console.log('Current age:', this.age);
1270+
}
1271+
}
1272+
1273+
const user = new User(2023);
1274+
```
1275+
1276+
上面示例中,顶层属性`age`的初始化值依赖于实例属性`this.currentYear`。按照 TypeScript 的处理方法,初始化是在构造方法里面完成的,会输出结果为`25`。但是,按照 ES2022 标准的处理方法,初始化在声明顶层属性时就会完成,这时`this.currentYear`还等于`undefined`,所以`age`的初始化结果为`NaN`,因此最后输出的也是`NaN`
1277+
1278+
第二种情况与类的继承有关,子类声明的顶层属性在父类完成初始化。
1279+
1280+
```typescript
1281+
interface Animal {
1282+
animalStuff: any;
1283+
}
1284+
1285+
interface Dog extends Animal {
1286+
dogStuff: any;
1287+
}
1288+
1289+
class AnimalHouse {
1290+
resident: Animal;
1291+
1292+
constructor(animal:Animal) {
1293+
this.resident = animal;
1294+
}
1295+
}
1296+
1297+
class DogHouse extends AnimalHouse {
1298+
resident: Dog;
1299+
1300+
constructor(dog:Dog) {
1301+
super(dog);
1302+
}
1303+
}
1304+
```
1305+
1306+
上面示例中,类`DogHouse`继承自`AnimalHouse`。它声明了顶层属性`resident`,但是该属性的初始化是在父类`AnimalHouse`完成的。不同的设置运行下面的代码,结果将不一致。
1307+
1308+
```typescript
1309+
const dog = {
1310+
animalStuff: 'animal',
1311+
dogStuff: 'dog'
1312+
};
1313+
1314+
const dogHouse = new DogHouse(dog);
1315+
1316+
console.log(dogHouse.resident) // 输出结果将不一致
1317+
```
1318+
1319+
上面示例中,TypeScript 的处理方法,会使得`resident`属性能够初始化,所以输出参数对象的值。但是,ES2022 标准的处理方法是,顶层属性的初始化先于构造方法的运行。这使得`resident`属性不会得到赋值,因此输出为`undefined`
1320+
1321+
为了解决这个问题,同时保证以前代码的行为一致,TypeScript 从3.7版开始,引入了编译设置`useDefineForClassFields`。这个设置设为`true`,则采用 ES2022 标准的处理方法,否则采用 TypeScript 早期的处理方法。
1322+
1323+
它的默认值与`target`属性有关,如果输出目标设为`ES2022`或者更高,那么`useDefineForClassFields`的默认值为`true`,否则为`false`。关于这个设置的详细说明,参见官方 3.7 版本的[发布说明](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#the-usedefineforclassfields-flag-and-the-declare-property-modifier)
1324+
1325+
如果希望避免这种不一致,让代码在不同设置下的行为都一样,那么可以将所有顶层属性的初始化,都放到构造方法里面。
1326+
1327+
```typescript
1328+
class User {
1329+
age: number;
1330+
1331+
constructor(private currentYear: number) {
1332+
this.age = this.currentYear - 1998;
1333+
console.log('Current age:', this.age);
1334+
}
1335+
}
1336+
1337+
const user = new User(2023);
1338+
```
1339+
1340+
上面示例中,顶层属性`age`的初始化就放在构造方法里面,那么任何情况下,代码行为都是一致的。
1341+
1342+
对于类的继承,还有另一种解决方法,就是使用`declare`命令,去声明子类顶层属性的类型,告诉 TypeScript 这些属性的初始化由父类实现。
1343+
1344+
```typescript
1345+
class DogHouse extends AnimalHouse {
1346+
declare resident: Dog;
1347+
1348+
constructor(dog:Dog) {
1349+
super(dog);
1350+
}
1351+
}
1352+
```
1353+
1354+
上面示例中,`resident`属性的类型声明前面用了`declare`命令。这种情况下,这一行代码在编译成 JavaScript 后就不存在,那么也就不会有行为不一致,无论是否设置`useDefineForClassFields`,输出结果都是一样的。
1355+
12811356
## 静态成员
12821357

12831358
类的内部可以使用`static`关键字,定义静态成员。

docs/tsconfig.json.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -819,6 +819,12 @@ class User {
819819

820820
如果`"types": []`,就表示不会自动将所有`@types`模块加入编译。
821821

822+
### useDefineForClassFields
823+
824+
`useDefineForClassFields`这个设置针对的是,在类(class)的顶部声明的属性。TypeScript 早先对这一类属性的处理方法,与写入 ES2022 标准的处理方法不一致。这个设置设为`true`,就用来开启 ES2022 的处理方法,设为`false`就是 TypeScript 原有的处理方法。
825+
826+
它的默认值跟`target`属性有关,如果编译目标是`ES2022`或更高,那么`useDefineForClassFields`默认值为`true`,否则为`false`
827+
822828
### useUnknownInCatchVariables
823829

824830
`useUnknownInCatchVariables`设置`catch`语句捕获的`try`抛出的返回值类型,从`any`变成`unknown`

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy