美文网首页
TypeScript学习之路——(3)class(类)

TypeScript学习之路——(3)class(类)

作者: helloyoucan | 来源:发表于2019-02-17 14:26 被阅读0次

    TypeScript v3.1

    公共,私有与受保护的修饰符

    public

    默认修饰符

    class Animal2{
        public name:string;
        public constructor(theName:string){
            this.name=theName
        }
        public move(distanceInMeters:number){
            console.log(distanceInMeters)
        }
    }
    
    private

    不能在声明它的类的外部访问

    class Animal3{
        private name:string;
        public constructor(theName:string){
            this.name=theName
        }
    }
    // new Animal3("Cat").name; //Error 属性“name”为私有属性,只能在类“Animal3”中访问。
    

    TypeScript使用的是结构性类型系统。
    当我们比较两种不同的类型时,并不在乎它们从何处而来,
    如果所有成员的类型都是兼容的,我们就认为它们的类型是兼容的。
    然而,当我们比较带有 private或 protected成员的类型的时候,情况就不同了。
    如果其中一个类型里包含一个 private成员,
    那么只有当另外一个类型中也存在这样一个 private成员,
    并且它们都是来自同一处声明时,
    我们才认为这两个类型是兼容的。 对于 protected成员也使用这个规则。
    下面来看一个例子,更好地说明了这一点:

    class Animal4{
        private name:string;
        constructor(theName:string){this.name=theName}
    }
    class Rhino extends Animal4{
        constructor(){super("Rhino")}
    }
    class Employee{
        private name:string;
        constructor(theName:string){this.name=theName}
    }
    let animal4 = new Animal4("Goat");
    let rhino = new Rhino();
    let employee = new Employee("Bob");
    animal4 = rhino
    

    //如果其中一个类型里包含一个 private成员,那么只有当另外一个类型中也存在这样一个 private成员它们都是来自同一处声明时,我们才认为这两个类型是兼容的。

    // animal4 = employee //Error 不能将类型“Employee”分配给类型“Animal4”。类型具有私有属性“name”的单独声明。
    
    protected

    protected修饰符与 private修饰符的行为很相似,但有一点不同, protected成员在派生类中仍然可以访问。

    class Person{
        protected name:string;
        constructor(name:string){this.name=name}
    }
    class Employee2 extends Person{
        private department:string;
        constructor(name:string,department:string){
            super(name)
            this.department = department
        }
        public getElevatorPitch(){
            return `Hello, my name is ${this.name} and I work in ${this.department}.`;
        }
    }
    let howard = new Employee2("Howard","HaHa");
    console.log(howard.getElevatorPitch())
    // console.log(howard.name) //Error 属性“name”受保护,只能在类“Person”及其子类中访问。
    

    //注意,我们不能在 Person类外使用 name,但是我们仍然可以通过 Employee类的实例方法访问,因为 Employee是由 Person派生而来的。

    //构造函数也可以被标记成 protected。 这意味着这个类不能在包含它的类外被实例化,但是能被继承

    class Person2 {
        protected name: string;
        protected constructor(theName: string) { this.name = theName; }
    }
    
    // Employee 能够继承 Person
    class Employee3 extends Person {
        private department: string;
    
        constructor(name: string, department: string) {
            super(name);
            this.department = department;
        }
    
        public getElevatorPitch() {
            return `Hello, my name is ${this.name} and I work in ${this.department}.`;
        }
    }
    
    let howard2= new Employee3("Howard", "Sales");
    // let john = new Person2("John"); // Error 'Person' 的构造函数是被保护的.
    
    readonly

    //使用 readonly关键字将属性设置为只读。 只读属性必须在声明时或构造函数里被初始化。

    class Octopus {
        readonly name: string;
        readonly numberOfLegs: number = 8;
        constructor (theName: string) {
            this.name = theName;
        }
    }
    let dad = new Octopus("Man with the 8 strong legs");
    // dad.name = "Man with the 3-piece suit"; // Error name 是只读的.
    

    参数属性

    在上面的例子中,
    我们必须在Octopus类里定义一个只读成员 name和
    一个参数为 theName的构造函数,
    并且立刻将 theName的值赋给 name,
    这种情况经常会遇到。
    参数属性可以方便地让我们在一个地方定义并初始化一个成员。
    下面的例子是对之前 Octopus类的修改版,使用了参数属性:

    class Octopus2{
       readonly numberOfLegs:number=8;
       constructor(readonly name:string){
    
       }
    }
    

    存取器

    //TypeScript支持通过getters/setters来截取对对象成员的访问。 它能帮助你有效的控制对对象成员的访问。
    //下面来看如何把一个简单的类改写成使用 get和 set。 首先,我们从一个没有使用存取器的例子开始。

    class Employee4 {
        fullName: string;
    }
    
    let employee4 = new Employee4();
    employee4.fullName = "Bob Smith";
    if (employee4.fullName) {
        console.log(employee4.fullName);
    }
    

    //我们可以随意的设置 fullName,这是非常方便的,但是这也可能会带来麻烦。

    下面这个版本里,我们先检查用户密码是否正确,然后再允许其修改员工信息。 我们把对 fullName的直接访问改成了可以检查密码的 set方法。 我们也加了一个 get方法,让上面的例子仍然可以工作。

    let passcode = "secret passcode"
    class Employee5{
        private _fullName:string;
    
        get fullName():string{
            return this._fullName
        }
    
        set fullName(newName:string){
            if(passcode&&passcode=="secret passcode"){
                this._fullName=newName
            }else{
                console.log("Error: Unauthorized update of employee!")
            }
        }
    }
    let employee5 = new Employee5()
    employee5.fullName="Bob smith"
    if(employee5.fullName){
        console.log(employee5.fullName)
    }
    

    //首先,存取器要求你将编译器设置为输出ECMAScript 5或更高。 不支持降级到ECMAScript 3。 其次,只带有 get不带有 set的存取器自动被推断为 readonly。 这在从代码生成 .d.ts文件时是有帮助的,因为利用这个属性的用户会看到不允许够改变它的值。

    静态属性

    class Grid{
        static origin = {x:0,y:0};
        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;
        }
        constructor (public scale: number) { }
    }
    let grid1 = new Grid(1.0);  // 1x scale
    let grid2 = new Grid(5.0);  // 5x scale
    
    console.log(grid1.calculateDistanceFromOrigin({x: 10, y: 10}));
    console.log(grid2.calculateDistanceFromOrigin({x: 10, y: 10}));
    

    抽象类

    //抽象类做为其它派生类的基类使用。
    //它们一般不会直接被实例化。
    //不同于接口,抽象类可以包含成员的实现细节。
    //abstract关键字是用于定义抽象类和在抽象类内部定义抽象方法。

    abstract class Animal5{
        abstract makeSound():void;
        move():void{
            console.log('roaming the earch...');
        }
    }
    

    抽象类中的抽象方法不包含具体实现并且必须在派生类中实现。
    抽象方法的语法与接口方法相似。
    两者都是定义方法签名但不包含方法体。
    然而,抽象方法必须包含 abstract关键字并且可以包含访问修饰符。

    abstract class Department{
        constructor(public name:string){
    
        }
        printName():void{
            console.log('Department name: ' + this.name);
        }
        abstract printMeeting(): void; // 必须在派生类中实现
    }
    class AccountingDepartment  extends Department{
        constructor() {
            super('Accounting and Auditing'); // 在派生类的构造函数中必须调用 super()
        }
        printMeeting(): void {
            console.log('The Accounting Department meets each Monday at 10am.');
        }
        generateReports(): void {
            console.log('Generating accounting reports...');
        }
    }
    let department: Department; // 允许创建一个对抽象类型的引用
    // department = new Department(); // Error 不能创建一个抽象类的实例
    department = new AccountingDepartment(); // 允许对一个抽象子类进行实例化和赋值
    department.printName();
    department.printMeeting();
    // department.generateReports(); // Error 方法在声明的抽象类中不存在
    

    高级技巧

    //构造函数
    //当你在TypeScript里声明了一个类的时候,实际上同时声明了很多东西。 首先就是类的 实例的类型

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

    //这里,我们写了 let greeter: Greeter,
    //意思是 Greeter类的实例的类型是 Greeter。
    //这对于用过其它面向对象语言的程序员来讲已经是老习惯了。

    //我们也创建了一个叫做 构造函数的值。
    //这个函数会在我们使用 new创建类实例的时候被调用。
    //下面我们来看看,上面的代码被编译成JavaScript后是什么样子的:

    let Greeter = (function () {
        function Greeter(message) {
            this.greeting = message;
        }
        Greeter.prototype.greet = function () {
            return "Hello, " + this.greeting;
        };
        return Greeter;
    })();
    
    let greeter;
    greeter = new Greeter("world");
    console.log(greeter.greet());
    

    上面的代码里, let Greeter将被赋值为构造函数。
    当我们调用 new并执行了这个函数后,便会得到一个类的实例。
    这个构造函数也包含了类的所有静态属性。
    换个角度说,我们可以认为类具有 实例部分与 静态部分这两个部分。

    //让我们稍微改写一下这个例子,看看它们之间的区别:

    class Greeter3 {
        static standardGreeting = "Hello, there";
        greeting: string;
        greet() {
            if (this.greeting) {
                return "Hello, " + this.greeting;
            }
            else {
                return Greeter3.standardGreeting;
            }
        }
    }
    
    let greeter3: Greeter3;
    greeter3 = new Greeter3();
    console.log(greeter3.greet());
    
    let greeterMaker: typeof Greeter3 = Greeter3;//???
    greeterMaker.standardGreeting = "Hey there!";
    
    let greeter4: Greeter = new greeterMaker();
    console.log(greeter4.greet());
    

    //这个例子里, greeter1与之前看到的一样。
    // 我们实例化 Greeter类,并使用这个对象。 与我们之前看到的一样。

    再之后,我们直接使用类。
    我们创建了一个叫做 greeterMaker的变量。
    这个变量保存了这个类或者说保存了类构造函数。
    然后我们使用 typeof Greeter,
    意思是取Greeter类的类型,
    而不是实例的类型。
    或者更确切的说,"告诉我 Greeter标识符的类型",
    也就是构造函数的类型。
    这个类型包含了类的所有静态成员和构造函数。
    之后,就和前面一样,
    我们在 greeterMaker上使用 new,创建 Greeter的实例。

    把类当做接口使用

    //如上一节里所讲的,类定义会创建两个东西:类的实例类型和一个构造函数。
    //因为类可以创建出类型,所以你能够在允许使用接口的地方使用类。

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

    相关文章

      网友评论

          本文标题:TypeScript学习之路——(3)class(类)

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