美文网首页
TypeScript中的private、protected

TypeScript中的private、protected

作者: 泡泡机不冒泡了 | 来源:发表于2021-01-28 20:21 被阅读0次

    首先我们要清楚 privateprotected 现阶段只是javascript中的保留字(Reserved words),而非关键字(Keywords)。因此TypeScript中的纯类型声明语句,编译后都会被擦除。

    class Person {
      public name: string;
      protected age: number; 
      private isMarried: boolean;
    }
    //编译结果
    class Person {
    }
    

    TypeScript是一个结构类型语言。当比较两个不同的类型时,不管它们来自哪里,如果所有成员的类型都是兼容的,那么就说这些类型本身是兼容的。

    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

    但是,当比较具有 private 成员或 protected 成员的类型时,会区别对待这些类型。如果其中一种类型具有private成员,那么另一种类型必须具有来源于同一处声明的private成员。这同样适用于protected成员。

    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,这里只是做个简要的引子。

    TypeScript 在判断类型兼容时,为什么处理 privateprotected 的规则要有别于 public , 这究竟有什么潜在的好处。

    假设有这样一个场景,目前电动汽车尚且处于发展的初级阶段,汽车品牌特斯拉、蔚来的最大里程数 maxMileage 值一样。

    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是结构式语言,因TeslaNio又有着相同名称、类型的字段 maxMileage ,即使 drive 入参声明为 Tesla 类型,也能通过校验。目前而言,即使误用,drive 的表现一样,不会有问题,但随着技术的发展,两个品牌的 maxMileage 值将不一样,drive 的行为也将千差万别。这个bug将一直潜伏着,直到引起严重故障才会引起关注。

    在上例基础上增加1) 2) 两处,多了 private(protected亦可) 声明的 brand 属性,来解决结构一样,但又想区分类型的场景,达到类似声明式类型系统的效果。这里就是利用了privateprotected属性必须源于同一处声明才可判定类型兼容。

    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 属性,增加了运行时开销,如果这不是你想要的,可以如下处理:

    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 语义上是私有的,对子类不可见,所以不能进行覆盖,而protectedpublic 语义上就是对子类可见的,子类知道当前在进行覆盖行为,这只是一方面。

    我们假设 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 类声明继承时的报错信息和前面的一样。

    相关文章

      网友评论

          本文标题:TypeScript中的private、protected

          本文链接:https://www.haomeiwen.com/subject/vbbxtltx.html