最新公告
  • 欢迎您光临网站无忧模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • TypeScript学习笔记(七)- 类与接口

    正文概述 掘金(转眼岁岁又年年)   2021-01-04   416

    TypeScript中类的用法

    public private 和 protected

    TypeScript 可以使用三种访问修饰符(Access Modifiers),分别是 public、private 和 protected。

    public 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 的 private 修饰的属性或方法是私有的,不能在声明它的类的外部访问 protected 修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类中也是允许被访问的

    class Animal {
      public name;
      public constructor(name) {
        this.name = name;
      }
    }
    
    let a = new Animal('Jack');
    console.log(a.name); // Jack
    a.name = 'Tom';
    console.log(a.name); // Tom
    

    上面的例子中,name 被设置为了 public,所以直接访问实例的 name 属性是允许的。

    我们希望有的属性是无法直接存取的,这时候就可以用 private 了:

    class Animal {
      private name;
      public constructor(name) {
        this.name = name;
      }
    }
    
    let a = new Animal('Jack');
    console.log(a.name); // Jack
    a.name = 'Tom';
    
    // index.ts(9,13): error TS2341: Property 'name' is private and only accessible within class 'Animal'.
    // index.ts(10,1): error TS2341: Property 'name' is private and only accessible within class 'Animal'.
    

    TypeScript 编译之后的代码中,并没有限制 private 属性在外部的可访问性。

    上面的例子编译后的代码是:

    var Animal = (function () {
      function Animal(name) {
        this.name = name;
      }
      return Animal;
    })();
    var a = new Animal('Jack');
    console.log(a.name);
    a.name = 'Tom';
    

    使用 private 修饰的属性或方法,在子类中也是不允许访问的:

    class Animal {
      private name;
      public constructor(name) {
        this.name = name;
      }
    }
    
    class Cat extends Animal {
      constructor(name) {
        super(name);
        console.log(this.name);
      }
    }
    
    // index.ts(11,17): error TS2341: Property 'name' is private and only accessible within class 'Animal'.
    

    而如果是用 protected 修饰,则允许在子类中访问:

    class Animal {
      protected name;
      public constructor(name) {
        this.name = name;
      }
    }
    
    class Cat extends Animal {
      constructor(name) {
        super(name);
        console.log(this.name);
      }
    }
    

    当构造函数修饰为 private 时,该类不允许被继承或者实例化:

    class Animal {
      public name;
      private constructor(name) {
        this.name = name;
      }
    }
    class Cat extends Animal {
      constructor(name) {
        super(name);
      }
    }
    
    let a = new Animal('Jack');
    
    // index.ts(7,19): TS2675: Cannot extend a class 'Animal'. Class constructor is marked as private.
    // index.ts(13,9): TS2673: Constructor of class 'Animal' is private and only accessible within the class declaration.
    

    当构造函数修饰为 protected 时,该类只允许被继承:

    class Animal {
      public name;
      protected constructor(name) {
        this.name = name;
      }
    }
    class Cat extends Animal {
      constructor(name) {
        super(name);
      }
    }
    
    let a = new Animal('Jack');
    
    // index.ts(13,9): TS2674: Constructor of class 'Animal' is protected and only accessible within the class declaration.
    

    参数属性

    修饰符和readonly还可以使用在构造函数参数中,等同于类中定义该属性同时给该属性赋值,使代码更简洁。

    class Animal {
      // public name: string;
      public constructor(public name) {
        // this.name = name;
      }
    }
    

    readonly

    只读属性关键字,只允许出现在属性声明或索引签名或构造函数中。

    class Animal {
      readonly name;
      public constructor(name) {
        this.name = name;
      }
    }
    
    let a = new Animal('Jack');
    console.log(a.name); // Jack
    a.name = 'Tom';
    
    // index.ts(10,3): TS2540: Cannot assign to 'name' because it is a read-only property.
    

    注意如果 readonly 和其他访问修饰符同时存在的话,需要写在其后面。

    class Animal {
      // public readonly name;
      public constructor(public readonly name) {
        // this.name = name;
      }
    }
    

    抽象类

    abstract 用于定义抽象类和其中的抽象方法。

    什么是抽象类?

    首先,抽象类是不允许被实例化的:

    abstract class Animal {
      public name;
      public constructor(name) {
        this.name = name;
      }
      public abstract sayHi();
    }
    
    let a = new Animal('Jack');
    
    // index.ts(9,11): error TS2511: Cannot create an instance of the abstract class 'Animal'.
    

    上面的例子中,我们定义了一个抽象类 Animal,并且定义了一个抽象方法 sayHi。在实例化抽象类的时候报错了。

    其次,抽象类中的抽象方法必须被子类实现:

    abstract class Animal {
      public name;
      public constructor(name) {
        this.name = name;
      }
      public abstract sayHi();
    }
    
    class Cat extends Animal {
      public eat() {
        console.log(`${this.name} is eating.`);
      }
    }
    
    let cat = new Cat('Tom');
    
    // index.ts(9,7): error TS2515: Non-abstract class 'Cat' does not implement inherited abstract member 'sayHi' from class 'Animal'.
    

    上面的例子中,我们定义了一个类 Cat 继承了抽象类 Animal,但是没有实现抽象方法 sayHi,所以编译报错了。

    下面是一个正确使用抽象类的例子:

    abstract class Animal {
      public name;
      public constructor(name) {
        this.name = name;
      }
      public abstract sayHi();
    }
    
    class Cat extends Animal {
      public sayHi() {
        console.log(`Meow, My name is ${this.name}`);
      }
    }
    
    let cat = new Cat('Tom');
    

    上面的例子中,我们实现了抽象方法 sayHi,编译通过了。

    需要注意的是,即使是抽象方法,TypeScript 的编译结果中,仍然会存在这个类,上面的代码的编译结果是:

    var __extends =
      (this && this.__extends) ||
      function (d, b) {
        for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
        function __() {
          this.constructor = d;
        }
        d.prototype = b === null ? Object.create(b) : ((__.prototype = b.prototype), new __());
      };
    var Animal = (function () {
      function Animal(name) {
        this.name = name;
      }
      return Animal;
    })();
    var Cat = (function (_super) {
      __extends(Cat, _super);
      function Cat() {
        _super.apply(this, arguments);
      }
      Cat.prototype.sayHi = function () {
        console.log('Meow, My name is ' + this.name);
      };
      return Cat;
    })(Animal);
    var cat = new Cat('Tom');
    

    类的类型

    给类加上 TypeScript 的类型很简单,与接口类似:

    class Animal {
      name: string;
      constructor(name: string) {
        this.name = name;
      }
      sayHi(): string {
        return `My name is ${this.name}`;
      }
    }
    
    let a: Animal = new Animal('Jack');
    console.log(a.sayHi()); // My name is Jack
    

    类实现接口

    interface Alarm {
        alert(): void;
    }
    
    class Door {
    }
    
    class SecurityDoor extends Door implements Alarm {
        alert() {
            console.log('SecurityDoor alert');
        }
    }
    
    class Car implements Alarm {
        alert() {
            console.log('Car alert');
        }
    }
    

    一个类可以实现多个接口:

    interface Alarm {
        alert(): void;
    }
    
    interface Light {
        lightOn(): void;
        lightOff(): void;
    }
    
    class Car implements Alarm, Light {
        alert() {
            console.log('Car alert');
        }
        lightOn() {
            console.log('Car light on');
        }
        lightOff() {
            console.log('Car light off');
        }
    }
    

    接口继承接口

    接口与接口之间可以是继承关系:

    interface Alarm {
        alert(): void;
    }
    
    interface LightableAlarm extends Alarm {
        lightOn(): void;
        lightOff(): void;
    }
    

    这很好理解,LightableAlarm 继承了 Alarm,除了拥有 alert 方法之外,还拥有两个新方法 lightOn 和 lightOff。

    接口继承类

    常见的面向对象语言中,接口是不能继承类的,但是在 TypeScript 中却是可以的:

    class Point {
        x: number;
        y: number;
        constructor(x: number, y: number) {
            this.x = x;
            this.y = y;
        }
    }
    
    interface Point3d extends Point {
        z: number;
    }
    
    let point3d: Point3d = {x: 1, y: 2, z: 3};
    

    为什么 TypeScript 会支持接口继承类呢?

    实际上,当我们在声明 class Point 时,除了会创建一个名为 Point 的类之外,同时也创建了一个名为 Point 的类型(实例的类型)。

    所以我们既可以将 Point 当做一个类来用(使用 new Point 创建它的实例):

    class Point {
        x: number;
        y: number;
        constructor(x: number, y: number) {
            this.x = x;
            this.y = y;
        }
    }
    
    const p = new Point(1, 2);
    

    也可以将 Point 当做一个类型来用(使用 : Point 表示参数的类型):

    class Point {
        x: number;
        y: number;
        constructor(x: number, y: number) {
            this.x = x;
            this.y = y;
        }
    }
    
    function printPoint(p: Point) {
        console.log(p.x, p.y);
    }
    
    printPoint(new Point(1, 2));
    

    这个例子实际上可以等价于:

    class Point {
        x: number;
        y: number;
        constructor(x: number, y: number) {
            this.x = x;
            this.y = y;
        }
    }
    
    interface PointInstanceType {
        x: number;
        y: number;
    }
    
    function printPoint(p: PointInstanceType) {
        console.log(p.x, p.y);
    }
    
    printPoint(new Point(1, 2));
    

    上例中我们新声明的 PointInstanceType 类型,与声明 class Point 时创建的 Point 类型是等价的。

    所以回到 Point3d 的例子中,我们就能很容易的理解为什么 TypeScript 会支持接口继承类了:

    class Point {
        x: number;
        y: number;
        constructor(x: number, y: number) {
            this.x = x;
            this.y = y;
        }
    }
    
    interface PointInstanceType {
        x: number;
        y: number;
    }
    
    // 等价于 interface Point3d extends PointInstanceType
    interface Point3d extends Point {
        z: number;
    }
    
    let point3d: Point3d = {x: 1, y: 2, z: 3};
    

    当我们声明 interface Point3d extends Point 时,Point3d 继承的实际上是类 Point 的实例的类型。

    声明 Point 类时创建的 Point 类型只包含其中的实例属性和实例方法:

    class Point {
        /** 静态属性,坐标系原点 */
        static origin = new Point(0, 0);
        /** 静态方法,计算与原点距离 */
        static distanceToOrigin(p: Point) {
            return Math.sqrt(p.x * p.x + p.y * p.y);
        }
        /** 实例属性,x 轴的值 */
        x: number;
        /** 实例属性,y 轴的值 */
        y: number;
        /** 构造函数 */
        constructor(x: number, y: number) {
            this.x = x;
            this.y = y;
        }
        /** 实例方法,打印此点 */
        printPoint() {
            console.log(this.x, this.y);
        }
    }
    
    interface PointInstanceType {
        x: number;
        y: number;
        printPoint(): void;
    }
    
    let p1: Point;
    let p2: PointInstanceType;
    

    上例中最后的类型 Point 和类型 PointInstanceType 是等价的。

    同样的,在接口继承类的时候,也只会继承它的实例属性和实例方法。

    参考

    TypeScript 入门教程 - ts.xcatliu.com/

    TypeScript 官网 - www.typescriptlang.org/


    下载网 » TypeScript学习笔记(七)- 类与接口

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元