作者回复: 先记住一个OOP的基本特性,就是:子类的实例可以赋值给父类。这是继承性决定的,也就是说:鸟是动物,决定了bird可以赋给Animal的实例x。例如: ``` // 注意类型T相当于Animal类型 let x: Animal = new Bird(); ``` 但上述赋值发生之后,请问x是什么类型?x是它声明的类型Animal,还是它赋值后的实体类型Bird?在强类型系统中,除非显式转换,x的类型总是按声明的Animal来理解的——这也是“静态类型系统”的本意。所以,无论这里的x是赋值了bird还是horse,它在后续的运算中,都只是理解为显式声明的Animal类型。这也就是你尝试存取x1.wings时会报错的原因。 TS会提供一种“类型收窄”的技术,使JS逻辑中(注意这里是JS逻辑)的类型识别在特定的上下文中可以将实例的类型——例如这里的x/x1——识别为“收窄后的类型”。这个要在第13讲中才讲。而所谓“收窄”,就是让JS在执行过程中将x/x1的类型识别为“更具体”的类型,从而完成你上述的取值(或向x1.wings成员赋值)的操作。例如: ``` // 声明守护函数isBird() function isBird(x) ... // 类型识别与收窄(第13讲) if (isBird(x1)) { console.log(x1.wing); // success } ``` NOTE1:“子类的实例可以赋值给父类”同样可以放到子类型兼容的概念中去讲,这也是“子类型可以赋给父类型(例如将字符串字面量赋给string类型)”的原因。同样,这个逻辑,也就是“更具体的类型可以赋给更抽象的类型”的原因,鸟(bird)是一个更具体的“动物(Animal)”,又或者“生物”或“物”是相对于动物更抽象的数据概念,或类型。 NOTE2: 类型T与类型Animal本质上还是有些不同的。事实上因为类型T是通过Omit<>计算出来的,所以它与Bird/Horse并没有显式的继承关系,在做兼容性计算时用的是“结构性兼容”。如果是Animal类型,并用extends声明过与Bird/Horse的继承性关系,那么计算时会是“子类型兼容”。——尽管在这个例子中,这两种处理方法的结果一致,但概念上还是不同的。