TypeScript 04 - 类

作者: 晓风残月1994 | 来源:发表于2019-11-28 20:09 被阅读0次
    1. 基本示例
    2. 继承
    3. 公共、私有与受保护的修饰符
    4. readonly 修饰符
    5. 存取器
    6. 静态属性
    7. 抽象类
    8. 高级技巧

    1. 基本示例

    class Greeter {
      greeting: string;
      constructor(message: string) {
        this.greeting = message;
      }
    
      greet() {
        return 'Hello, ' + this.greeting;
      }
    }
    
    let greeter = new Greeter('world');
    console.log(greeter.greet());
    

    2. 继承

    继承时子类要拥有匹配的构造器,super 关键字直接调用时等价于调用父类构造函数,而 super.props 可以访问父对象上的方法成员。在创建子类实例时,要先调用 super 来生成匹配的父类,并且必须在使用 this 关键字之前使用。 然后才进一步给当前子类自己加戏。

    class Animal {
      name: string;
    
      constructor(name: string) {
        this.name = name;
      }
    
      move(distance: number = 0) {
        console.log(`${this.name} moved ${distance}`);
      }
    }
    
    
    class Snake extends Animal {
      constructor(name: string) {
        super(name);
      }
    
      move(distance: number = 5) {
        console.log('蛇皮走位ing...');
        super.move(distance);
      }
    }
    
    
    class Horse extends Animal {
      constructor(name: string) {
        super(name);
      }
    
      move(distance: number = 45) {
        console.log('奔腾ing...')
        super.move(distance);
      }
    }
    
    
    let tony = new Snake('Tony');
    let tom: Animal = new Horse('Tom');
    
    tony.move();
    tom.move(75);
    // 蛇皮走位ing...
    // Tony moved 5
    // 奔腾ing...
    // Tom moved 75
    

    3. 公共、私有与受保护的修饰符

    成员默认都是 public 公共的,可以在外部任意访问。

    3.1 private 私有修饰符

    private 修饰的成员只能在类内部访问,private 修饰的构造器也只有在内部才能使用 new 操作符来实例化:

    class Animal {
      protected name: string;
    
      constructor(name: string) {
        this.name = name;
      }
    }
    
    new Animal('Cat').name; // 报错
    
    
    class Pig extends Animal {
      constructor() {
        super('Peng');
      }
    }
    
    
    class Employee {
      protected name: string;
    
      constructor(name: string) {
        this.name = name;
      }
    }
    
    // 私有成员来源不一样,不能互相兼容,除非成员完全一样
    
    let animal = new Animal('name');
    let pig = new Pig();
    let employee = new Employee('Bob');
    
    animal = pig; // 子类可以赋值给父类
    animal = employee;
    // 报错,就算 Employee 看起来和 Animal 定义的一样,但是因为存在用 private 修饰的各自私有属性,所以两个类不能等价
    // 除非 name 都是 public,不再私有了,才能认为所指的是同一个属性 
    

    3.2 protected 受保护的修饰符

    class Person {
      protected name: string;
    
      protected constructor(name: string) {
        this.name = name;
      }
    }
    
    
    class Employee extends Person {
      private department: string;
    
      constructor(name: string, department: string) {
        super(name);
        this.department = department;
      }
    
      getEmployeeInfo() {
        new Person('wangpeng')
        return `Hello, my name is ${this.name} and I work in ${this.department}.`;
      }
    }
    
    
    let tony = new Employee('name', 'Sales');
    console.log(tony.getEmployeeInfo());
    // Hello, my name is name and I work in Sales
    
    
    console.log(tony.name);
    // 报错,受保护的(protected)属性 name 来自于超类 Person,只能在 Person 类中和其子类中访问
    let bob = new Person('peng');
    // 报错,同理,因为 Person 类的构造器也是 protected
    

    4. readonly 只读修饰符

    readonly 类似于是 Java 中的 final 关键字,用来修饰只能被赋值一次、之后只读的值。

    
    class Person {
      readonly name: string;
    
      constructor(name: string) {
        this.name = name;  
      }
    }
    
    
    // Person 类的构造器中还可以使用参数属性,把声明和赋值合并为一个操作,但是不推荐使用
    class Person {
      constructor( readonly name: string) {
        this.name = name;  
      }
    }
    
    
    let tom = new Person('Tom');
    console.log(tom.name);
    
    tom.name = 'Sam'; // 报错,只读属性不能修改
    

    5. 存取器

    编译的时候要指定为 ES5 或更高版本,方能支持访问器。

    编译时指定 ES5 版本:

    tsc index.ts --target es5
    

    编译成 ES5 后,实际上是利用了 Object.defineProperty() 来定义访问器属性描述符,这也叫访问劫持(这些专业术语总是如此专业):

    var password = 'daohaotiandaleipi';
    var Employee = /** @class */ (function () {
        function Employee() {
        }
        Object.defineProperty(Employee.prototype, "fullName", {
            get: function () {
                return this._fullName;
            },
            set: function (newName) {
                if (password && password === 'daohaotiandaleipi') {
                    this._fullName = newName;
                }
                else {
                    console.log('Error: Unauthorized update of employee!');
                }
            },
            enumerable: true,
            configurable: true
        });
        return Employee;
    }());
    var employee = new Employee();
    employee.fullName = 'Tony'; // 设置前时候会校验密码
    if (employee.fullName) {
        console.log(employee.fullName);
    }
    

    下面指定编译为 ES6 版本,和 TS 源码区别不大,基本只是把类型系统移除,毕竟 ES6 的 class 本身就支持这样的访问器语法:

    let password = 'daohaotiandaleipi';
    class Employee {
        get fullName() {
            return this._fullName;
        }
        set fullName(newName) {
            if (password && password === 'daohaotiandaleipi') {
                this._fullName = newName;
            }
            else {
                console.log('Error: Unauthorized update of employee!');
            }
        }
    }
    let employee = new Employee();
    employee.fullName = 'Tony'; // 设置前时候会校验密码
    if (employee.fullName) {
        console.log(employee.fullName);
    }
    

    6. 静态属性

    静态属性存在类本身上,而不是实例上。类其实也是一个对象,使用 SomeClass.someProperty 这种形式来读取静态属性。

    class Grid {
      static origin = { x: 0, y: 0 }; // 静态属性
    
      scale: number; // 缩放比例
    
      constructor(scale: number) {
        this.scale = scale;
      }
    
      calculateDistanceFromOrigin(point: { x: number; y: number }) {
        let xDist = point.x - Grid.origin.x;
        let yDist = point.y - Grid.origin.y;
        return Math.sqrt(xDist * xDist + yDist * yDist) * this.scale;
      }
    }
    
    let grid1 = new Grid(1.0);
    console.log(grid1.calculateDistanceFromOrigin({ x: 3, y: 4 }));
    
    let grid2 = new Grid(5.0);
    console.log(grid2.calculateDistanceFromOrigin({ x: 3, y: 4 }));
    

    7. 抽象类

    抽象类作为其他派生类的基类,不能直接进行实例化,由其派生类来实例化,抽象类可以提供抽象的方法签名,也可以提供一些方法的默认实现

    abstract class Department {
      name: string;
    
      constructor(name: string) {
        this.name = name;
      }
    
      printName(): void {
        console.log('Department name: ' + this.name);
      }
    
      abstract printMeeting(): void;
    }
    
    
    class AccountingDepartment extends Department {
      constructor() {
        super('会计事务所');
      }
    
      printMeeting(): void {
        console.log('每天十点开会');
      }
    
      generateReports(): void {
        console.log('生成报告中...');
      }
    
      // printName(): void {
      //   console.log('Department name: ' + '我瞎编的部门');
      // }
    }
    
    let department: Department;
    
    department = new Department(); // 报错,不能直接实例化抽象类
    
    department = new AccountingDepartment();
    department.printName();
    department.printMeeting();
    
    // 报错,generateReports 并不存在于 Department 抽象类中,而是存在于 AccountingDepartment
    // 除非把 department 类型声明为 AccountingDepartment
    department.generateReports();
    

    8. 高级技巧

    8.1 构造函数

    如之前了解的那样,类具有静态部分和实例部分:

    class Greeter {
      static standardGreeting = 'Hello, there';
      greeting: string;
      greet() {
        if (this.greeting) {
          return 'Hello, ' + this.greeting;
        } else {
          return Greeter.standardGreeting;
        }
      }
    }
    
    let greeter1: Greeter; // 声明 greeter1 是 Greeter 类的实例类型,也叫 Greeter
    greeter1 = new Greeter();
    console.log(greeter1.greet());
    

    那如果想声明一个变量let greeterMaker,将来会用来存储一个类(在 JS 这门语言中其实就是构造函数),那么 TS 中当给该变量指定类型时, let greeterMaker: Greeter 这样是不行的,这意思曲解成了变量 greeterMaker 的类型是 Greeter 的实例类型。

    参见下方代码,此时,可以使用 typeof Greeter 来表明这里我需要的是 Greeter 类本身的类型,或者说是 Greeter 构造函数本身的类型(而不是其实例的类型),这个类型包含了类的所有静态成员和构造函数。

    当然如果是声明变量的同时就进行了赋值,那么即使不显式地指出 greeterMaker 的类型,在类似 vscode 这种编辑器中 ,使用鼠标 hover 的时候也会显示 TS 自动推断出的类型:

    接着就可以 new greeterMaker()

    把下面代码接着写在上一段代码后面:

    let greeterMaker: typeof Greeter;
    greeterMaker = Greeter;
    greeterMaker.standardGreeting = 'Hey there';
    
    let greeter2: Greeter = new greeterMaker();
    console.log(greeter2.greet());
    
    console.log(greeter1.greet());
    

    总的输出结果如下,说明其实 greeterMakerGreeter 引用的都是同一个值

    Hello, there
    Hey hahah
    Hey hahah
    

    8.2 把类当做接口使用

    就像上一章提到的接口继承类,虽然类能当做接口使用,并且能被接口继承,但通常不建议这样做。

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

    相关文章

      网友评论

        本文标题:TypeScript 04 - 类

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