首先咱们要清楚 private
、 protected
现阶段只是javascript
中的保留字(Reserved words
),而非关键字(Keywords
)。所以TypeScript
中的纯类型声明语句,编译后都会被擦除。javascript
class Person { public name: string; protected age: number; private isMarried: boolean; } //编译结果 class Person { }
TypeScript是一个结构类型语言。当比较两个不一样的类型时,无论它们来自哪里,若是全部成员的类型都是兼容的,那么就说这些类型自己是兼容的。html
interface Named { name: string; } class Bar { name: string; } class Foo { name: string; } // OK, because of structural typing let a: Named = new Person(); //✔️ let b: Foo = new Bar(); //✔️
因为 TypeScript
属性声明默认是 public
,因此上面能够以 b.name
形式访问,而java
则默认是protected
。java
可是,当比较具备 private
成员或 protected
成员的类型时,会区别对待这些类型。若是其中一种类型具备private成员,那么另外一种类型必须具备来源于同一处声明的private成员。这一样适用于protected成员。typescript
class Bar { private name: string; } class Foo { private name: string; } let bar: Bar = new Foo(); // ❌ //Type 'Foo' is not assignable to type 'Bar'. //Types have separate declarations of a private property 'name'.
上面的这些概念规则来源于 TypeScript Handbook,这里只是作个简要的引子。this
TypeScript
在判断类型兼容时,为何处理 private
、protected
的规则要有别于 public
, 这究竟有什么潜在的好处。code
假设有这样一个场景,目前电动汽车尚且处于发展的初级阶段,汽车品牌特斯拉、蔚来的最大里程数 maxMileage
值同样。htm
interface Car { maxMileage: number; } class Tesla implements Car { maxMileage: number = 500; } class Nio implements Car { maxMileage: number = 500; } function drive(car :Tesla) { console.log(car.maxMileage) } let tesla = new Tesla(); let nio = new Nio(); drive(tesla); // ✔️ drive(nio); // ✔️
因为TypeScript
是结构式语言,因Tesla
、Nio
又有着相同名称、类型的字段 maxMileage
,即便 drive
入参声明为 Tesla
类型,也能经过校验。目前而言,即便误用,drive
的表现同样,不会有问题,但随着技术的发展,两个品牌的 maxMileage
值将不同,drive
的行为也将千差万别。这个bug将一直潜伏着,直到引发严重故障才会引发关注。继承
在上例基础上增长1) 2) 两处,多了 private
(protected
亦可) 声明的 brand
属性,来解决结构同样,但又想区分类型的场景,达到相似声明式类型系统的效果。这里就是利用了private
、protected
属性必须源于同一处声明才可断定类型兼容。ip
class Tesla implements Car { private brand: string = "Tesla"; // 1) maxMileage: number = 500; } class Nio implements Car { private brand: string = "Tesla"; //2) maxMileage: number = 500; } function drive(car :Tesla) { console.log(car.maxMileage) } let tesla = new Tesla(); let nio = new Nio(); drive(tesla); // ✔️ drive(nio); // ❌ //Argument of type 'Nio' is not assignable to parameter of type 'Tesla'. //Types have separate declarations of a private property 'brand'. //编译后 class Tesla { constructor() { this.brand = "Tesla"; this.maxMileage = 500; } } class Nio { constructor() { this.brand = "Tesla"; this.maxMileage = 500; } }
虽然达到了咱们想要的效果,但类实例会多出 brand
属性,增长了运行时开销,若是这不是你想要的,能够以下处理:ci
class Tesla implements Car { //@ts-ignore private brand: string; maxMileage: number = 500; } class Nio implements Car { //@ts-ignore private brand: string ; maxMileage: number = 500; } //编译后 class Tesla { constructor() { this.maxMileage = 500; } } class Nio { constructor() { this.maxMileage = 500; } }
能够看到编译后的代码很纯净了。//@ts-ignore
仅在 strictPropertyInitialization: true
时须要,避免因未初始化属性而编译报错。
Types have separate declarations of a private property
报错还会出如今类extends继承的时候。初看很奇怪,使用姿式不一样,但报错信息且相似。
class ElectricVehicle { private charge() {}; } //Type 'FF91' is not assignable to type 'ElectricVehicle'. // Types have separate declarations of a private property 'charge' class FF91 extends ElectricVehicle { // ❌ private charge() {}; }
经过将 private
改为 protected或public
能够修复。不少文章会提到这是因为 private
语义上是私有的,对子类不可见,因此不能进行覆盖,而protected
、public
语义上就是对子类可见的,子类知道当前在进行覆盖行为,这只是一方面。
咱们假设 TypeScript
容许覆盖 private
方法,上面的类声明编译经过。但当咱们执行下面语句时,上面的报错再次出现。
let parent = new ElectricVehicle(); let child = new FF91(); parent = child; // ❌ //Type 'FF91' is not assignable to type 'ElectricVehicle'. // Types have separate declarations of a private property 'charge'
最初的示例,Foo、Bar
只是两个结构相似的类,并没有继承关系,断定类型不兼容尚可理解。这里父子类之间类型不兼容就无法自圆了。
因此编译器提早在类声明时就报错,避免延后到使用阶段。这也是为何 FF91
类声明继承时的报错信息和前面的同样。