@@ -954,65 +954,6 @@ class Test extends getGreeterBase() {
954
954
955
955
上面示例中,例一和例二的` extends ` 关键字后面都是构造函数,例三的` extends ` 关键字后面是一个表达式,执行后得到的也是一个构造函数。
956
956
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
-
1016
957
## 可访问性修饰符
1017
958
1018
959
类的内部成员的外部可访问性,由三个可访问性修饰符(access modifiers)控制:` public ` 、` private ` 和` protected ` 。
@@ -1278,6 +1219,140 @@ class A {
1278
1219
}
1279
1220
```
1280
1221
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
+
1281
1356
## 静态成员
1282
1357
1283
1358
类的内部可以使用` static ` 关键字,定义静态成员。
0 commit comments