@@ -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 ` 关键字,定义静态成员。
0 commit comments