美文网首页我爱编程
TypeScript 旅途3:类

TypeScript 旅途3:类

作者: 工匠前沿 | 来源:发表于2018-01-28 17:35 被阅读0次

    传统的JavaScript程序使用函数和基于原型的继承来创建可重用的组件,但是我们更习惯于使用面向对象的方式来开发。ES6已经可以使用class关键字进行面向对象的方式开发了,使用TypeScript不管要编译到哪个ES规范版本的JavaScript,都可以使用class等一系列关键字来进行面向对象的方式开发,功能更强大。

    // 类
    class FirstClass {
        msg: string;
        //构造函数
        constructor(msg: string) {
            this.msg = msg;
        }
        Show() {
            console.log('Hello ' + this.msg);
        }
    }
    let first = new FirstClass('world');
    first.Show();
    // 输出
    // Hello world
    

    class 关键字定义一个类。
    constructor 定义一个构造函数。一个构造函数就是你用 new 关键字创建一个类的实例时会调用的方法。

    继承

    面向对象的三个要素是封装继承多态。封装已经体现在类的定义中了,通过继承可以扩展一个类的功能,也可以体现多态这一特性。

    class A {
        a: string;
        constructor(a: string) {
            this.a = a;
        }
        show() {
            console.log(this.a + ' from A')
        }
    }
    
    class B extends A {
        b: string;
        constructor(a: string, b: string) {
            super(a);//调用父类构造函数
            this.b = b;
        }
        show() {
            console.log(this.a + ' from B ');
        }
    }
    let a: A = new A('a');
    a.show();//a from A
    let b: B = new B('a', 'b');
    b.show();//a from B
    let c: A = new B('c', 'b');
    c.show();//c from B
    //console.log(c.b);//错误,无法访问派生类的属性
    
    • 继承使用关键字extendsB 继承 A ,则称A是父类,B 是子类。
    • 父类中有构造函数,则子类的构造函数必须调用父类的构造函数。使用关键字 super 调用父类的构造函数,这个函数在子类构造函数中必须放到最开始的地方。
    • 用父类的类型实例化一个子类会导致访问不到子类的属性。let c: A = new B('c', 'b'); 这里对象 c 没法访问子类中定义的属性 b
    • 面向对象中的 继承 强调的是子类与父类的相似性(从父类继承过来的属性与方法)以及扩展父类。而 多态 强调的是子类的不同形态,即跟父类不同的部分。

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

    public 表示公共的,外部可以访问的。如果不写修饰符,则默认是public。
    private 表示私有的,只有内部可以访问的。
    protected 表示受保护的,只有类与其子类中可以访问。

    class Animal {
        name: string
        protected readonly weight: number;
        private cry: boolean;
        protected constructor(name: string, weight: number) {
            this.name = name;
            this.weight = weight;
            this.cry = true;
        }
        show() {
            console.log(`${this.name} ${this.weight}, 
                can cry ${this.cry}`);
        }
    }
    
    class Dog extends Animal {
        constructor(name: string, weight: number) {
            super(name, weight);
        }
        run() {
            //cry是基类的private属性,子类无法访问
            //console.log(`${this.name} ${this.weight}, 
            //    can cry ${this.cry}`);
            console.log(`${this.name} ${this.weight}, 
                I can run`);
        }
    }
    
    class Bird1 extends Animal {
        constructor(name: string, weight: number) {
            super(name, weight);
        }
        fly() {
            console.log(`${this.name} ${this.weight}, 
                I can fly`);
        }
    }
    let dog = new Dog('Dog', 123);
    //console.log(dog.weight);//错误,外部无法访问protected定义的属性
    dog.run();
    //输出
    /*
    Dog 123, 
    I can run
    */
    let bird = new Bird1('Bird1', 11.3);
    bird.fly();
    //输出
    /*
    Bird1 11.3, 
                I can fly
    */
    dog.show();
    bird.show();
    //输出
    /*
    Dog 123, 
                can cry true
    Bird1 11.3, 
                can cry true
    */
    
    • 父类中 publicprotected 的属性与方法在子类中都可以访问, private 定义的属性与方法无法访问(父类的 cry 属性子类无法访问)。在类的外部只能访问 public 定义的属性与方法(protected 属性无法访问)。
    • 父类的构造函数定义成 protected ,则无法使用这个父类实例化一个对象。
    class Bird2 {
        name: string
        protected readonly weight: number;
        private cry: boolean;
        constructor(name: string, weight: number) {
            this.name = name;
            this.weight = weight;
            this.cry = true;
        }
        show() {
            console.log(`${this.name} ${this.weight}, 
                can cry ${this.cry}`);
        }
        
        fly() {
            console.log(`${this.name} ${this.weight}, 
                I can fly`);
        }
    }
    let bird2 = new Bird2('Bird2', 13.1);
    //bird = bird2;//错误,不是从同一个基类派生,类型不兼容
    

    Bird2Bird1 拥有相同的属性与方法,不同的是 Bird1 是通过继承获得了一部分,而Bird2 是全部自己定义的。这两个类是不兼容的。如果想要类型兼容,除了要有相同的属性定义,还必须都是来自同一处声明时,才认为这两个类型是兼容的,也就是继承与同一个父类或实现了同一个接口。

    参数属性

    class Demo {
        constructor(private name: string) {
            console.log(this.name);
        }
    }
    let demo = new Demo('Demo');//Demo
    

    代码中并没有定义一个 name 属性,但是在构造函数中却可以访问,这是因为在构造函数中使用了参数属性,private name: string 来创建和初始化 name 属性。

    存取器

    如果想访问类中 protectedprivate 定义的属性要怎么办?

    class Demo1 {
        private _name: string;
        get name(): string {
            return this._name;
        }
        set name(name: string) {
            this._name = name;
        }
    }
    let demo1 = new Demo1();
    demo1.name = 'Demo1';
    console.log(demo1.name);//Demo1
    
    • 为了不破坏封装性,有些属性必须定义成 protectedprivate,但是在类的外部又需要调整这个属性,这就要用到get和 set来访问了。
    • get提供给外部访问属性的权限。
    • set提供给外部修改属性的权限。

    静态属性

    class Add {
        static num: number = 19;
        static show() {
            console.log(Add.num);
        }
        show() {
            console.log(Add.num);
        }
    }
    console.log(Add.num);//19
    Add.show();//19
    let add1 = new Add();
    let add2 = new Add();
    Add.num++;
    add1.show();//20
    add2.show();//20
    
    • 使用关键字 static 定义一个静态的属性或方法。静态属性是不属于任何对象的,必须使用类名加属性名的方式访问(如例子中:Add.show)。
    • 一个类的所有实例共享这个类中定义的静态属性。(例子中给静态属性加1后,所有的实例访问这个属性发现结果是一致的)。

    抽象类

    抽象类是不能实例化的类。关键字 abstract 定义一个抽象类或抽象方法。

    abstract class DemoA {
        readonly name: string;
        constructor(name: string) {
            this.name = name;
        }
        abstract show(): void;
    }
    class DemoB extends DemoA {
        show() {
            console.log(this.name);
        }
    }
    //错误,不能实例化一个抽象类
    //let demoA = new DemoA()
    let demoA: DemoA = new DemoB('DemoB');
    let demoB: DemoB = new DemoB('DemoB');
    demoA.show();//DemoB
    demoB.show();//DemoB
    
    • 抽象类不能用来实例化一个对象。
    • 抽象方法是没有方法体的,子类如果不是抽象类,就必须实现父类中的所有抽象方法。

    实现接口

    TypeScript能够用 interface 来明确的强制一个类去实现某种契约规范。一个类通过关键字 implements 来实现一个接口。

    enum Color {Red, Green, Blue};
    
    interface Point {
        readonly x: number;
        readonly y: number;
    }
    
    interface Bullet {
        name: string;//子弹名称
        damage: number;//子弹伤害值
        color: Color;//子弹颜色
        speed: number;//子弹速度
        bomb: boolean;//是否炸弹
        aa?: string;
        Shoot(from: Point, to: Point);
    }
    
    class Bullet1 implements Bullet {
        constructor(public name: string,
            public damage: number,
            public color: Color,
            public speed: number,
            public bomb: boolean) {
                
            }
        Shoot(from: Point, to: Point) {
            console.log(`bullet ${this.name}, 
                damage ${this.damage},
                color ${this.color}, 
                is bomb ${this.bomb}, 
                speed ${this.speed},
                shooting from (${from.x}, ${from.y})
                to (${to.x}, ${to.y})`);
        }
    }
    
    let bullet = new Bullet1('Bullet1', 11, Color.Blue, 33, false);
    bullet.Shoot({x: 10, y: 10}, {x: 100, y: 100});
    //输出
    /*
    bullet Bullet1, 
    damage 11,
    color 2, 
    is bomb false, 
    speed 33,
    shooting from (10, 10)
    to (100, 100)
    */
    
    • 接口就像是一个规范一样,接口中定义的属性除了可选属性以外,实现这个接口的类必须全部拥有。例子中在构造方法里面使用 参数属性的方式定义的这些属性。
    • 接口中定义的方法,类中必须实现。

    幻想的路总是为有信念的人准备着。

    相关文章

      网友评论

        本文标题:TypeScript 旅途3:类

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