TypeScript与面向对象

目录

1、引

简而言之就是程序之中所有的操作都需要通过对象来完成。一切操作都要通过对象,也就是所谓的面向对象,在程序中对象被分成了两个部分数据和功能,以人为例,人的姓名、性别、年龄、身高等属于数据,人可以说话、走路、吃饭、睡觉这些都属于人的功能,数据在对象中被称为属性,而功能被称为方法,所以简而言之在程序中一切皆对象

2、类(class)

// 使用class关键字来定义一个类
/*
*   对象中主要包含了两个部分:
*       属性
*       方法
* */
class Person{

    /*
    *   直接定义的属性是实例属性,需要通过对象的实例去访问:
    *       const per = new Person();
    *       per.name
    *
    *   使用static开头的属性是静态属性(类属性),可以直接通过类去访问
    *       Person.age
    *
    *   readonly开头的属性表示一个只读的属性无法修改
    * */

    // 定义实例属性
    // readonly name: string = '孙悟空';
    name = '孙悟空';

    // 在属性前使用static关键字可以定义类属性(静态属性)
    // static readonly age: number = 18;
    age = 18;


    // 定义方法
    /*
    * 如果方法以static开头则方法就是类方法,可以直接通过类去调用
    * */
    sayHello(){
        console.log('Hello 大家好!');
    }

}

const per = new Person();

// console.log(per);
// console.log(per.name, per.age);

// console.log(Person.age);

// console.log(per.name);
// per.name = 'tom';
// console.log(per.name);

// per.sayHello();

// Person.sayHello();
per.sayHello();

readonly 表示一个只读属性,不能修改,是在实例上的

static 表示一个静态属性,通过类的点语法

3、构造函数和this

class Dog{
    name: string;
    age: number;

    // constructor 被称为构造函数
    //  构造函数会在对象创建时调用
    constructor(name: string, age: number) {
        // 在实例方法中,this就表示当前当前的实例
        // 在构造函数中当前对象就是当前新建的那个对象
        // 可以通过this向新建的对象中添加属性
        this.name = name;//第一个name是类的属性,第二个name是传进的参数的类型
        this.age = age;
    }

    bark(){
        // alert('汪汪汪!');
        // 在方法中可以通过this来表示当前调用方法的对象
        console.log(this.name);
    }
}

const dog = new Dog('小黑', 4);
const dog2 = new Dog('小白', 2);

// console.log(dog);
// console.log(dog2);

dog2.bark();

4、继承

(function (){

    // 定义一个Animal类
    class Animal{
        name: string;
        age: number;

        constructor(name: string, age: number) {
            this.name = name;
            this.age = age;
        }

        sayHello(){
            console.log('动物在叫~');
        }
    }

    /*
    * Dog extends Animal
    *   - 此时,Animal被称为父类,Dog被称为子类
    *   - 使用继承后,子类将会拥有父类所有的方法和属性
    *   - 通过继承可以将多个类*有的代码写在一个父类中,
    *       这样只需要写一次即可让所有的子类都同时拥有父类中的属性和方法
    *       如果希望在子类中添加一些父类中没有的属性或方法直接加就行
    *   - 如果在子类中添加了和父类相同的方法,则子类方法会覆盖掉父类的方法,方法可以重写!!
    *       这种子类覆盖掉父类方法的形式,我们称为方法重写
    *
    * */
    // 定义一个表示狗的类
    // 使Dog类继承Animal类
    class Dog extends Animal{

        run(){
            console.log(`${this.name}在跑~~~`);
        }

        sayHello() {
            console.log('汪汪汪汪!');
        }

    }

    // 定义一个表示猫的类
    // 使Cat类继承Animal类
    class Cat extends Animal{
        sayHello() {
            console.log('喵喵喵喵!');
        }
    }

    const dog = new Dog('旺财', 5);
    const cat = new Cat('咪咪', 3);
    console.log(dog);
    dog.sayHello();
    dog.run();
    console.log(cat);
    cat.sayHello();


})();

5、super

子类写了constructor构造函数必须使用super继承父类constructor构造函数的属性

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

        sayHello() {
            console.log('动物在叫~');
        }
    }

    class Dog extends Animal{
        age: number;
        constructor(name: string, age: number) {
            // 如果在子类中写了构造函数,在子类构造函数中必须对父类的构造函数进行调用
            //super(name) === this.name = name,伪代码
            super(name); // 调用父类的构造函数
            //如果在子类的构造函数直接super父类构造函数
            this.age = age;
        }
        // 重写父类方法
        sayHello() {
            // 在类的方法中 super就表示当前类的父类
            // super.sayHello();
            console.log('汪汪汪汪!');
        }
    }

    const dog = new Dog('旺财', 3);
    dog.sayHello()
})();


6、抽象类

(function () {

    /*
    *   以abstract开头的类是抽象类,
    *       抽象类和其他类区别不大,只是不能用来创建对象
    *       抽象类就是专门用来被继承的类
    *
    *       抽象类中可以添加抽象方法
    * */
    abstract class Animal {
        name: string;

        constructor(name: string) {
            this.name = name;
        }

        // 定义一个抽象方法
        // 抽象方法使用 abstract开头,没有方法体
        // 抽象方法只能定义在抽象类中,子类必须对抽象方法进行重写
        abstract sayHello():void;
    }

    class Dog extends Animal{
        sayHello() {
            console.log('汪汪汪汪!');
        }

    }

    class Cat extends Animal{
        sayHello() {
            console.log('喵喵喵喵!');
        }

    }

    const dog = new Dog('旺财');
    dog.sayHello();

})();

抽象方法只能定义在抽象类中,子类必须对抽象方法进行重写

7、接口

接口的作用类似于抽象类,不同点在于接口中的所有方法和属性都是没有实值的,换句话说接口中的所有方法都是抽象方法。接口主要负责定义一个类的结构,接口可以去限制一个对象的接口,对象只有包含接口中定义的所有属性和方法时才能匹配接口。同时,可以让一个类去实现接口,实现接口时类中要保护接口中的所有属性。

  • 示例(检查对象类型):

    • interface Person{
          name: string;
          sayHello():void;
      }
      
      function fn(per: Person){
          per.sayHello();
      }
      
      fn({name:'孙悟空', sayHello() {console.log(`Hello, 我是 ${this.name}`)}});
      
      
  • 示例(实现)

    • interface Person{
          name: string;
          sayHello():void;
      }
      
      class Student implements Person{
          constructor(public name: string) {
          }
      
          sayHello() {
              console.log('大家好,我是'+this.name);
          }
      }
      

8、属性的封装

  • public
    • 修饰的属性可以再任意位置访问修改默认值
  • private
    • 私有属性,私有属性只能在类内部进行访问修改
  • protected
    • protected受包含的属性,只能在当前类和当前类的子类中访问
  • getter方法用来读取属性
  • setter方法迎来设置属性
    • getter和setter被统一称为属性的存储器,定义时在方法之前添加get和set,调用的时候直接通过点语法调用
(function (){
    // 定义一个表示人的类
    class Person{
        // TS可以在属性前添加属性的修饰符
        /*
        *   public 修饰的属性可以在任意位置访问(修改) 默认值
        *   private 私有属性,私有属性只能在类内部进行访问(修改)
        *       - 通过在类中添加方法使得私有属性可以被外部访问
        *   protected 受包含的属性,只能在当前类和当前类的子类中访问(修改)
        *
        * */
        private _name: string;
        private _age: number;

        constructor(name: string, age: number) {
            this._name = name;
            this._age = age;
        }

        /*
        *   getter方法用来读取属性
        *   setter方法用来设置属性
        *       - 它们被称为属性的存取器
        * */
        // TS中设置getter方法的方式
        get name(){
            // console.log('get name()执行了!!');
            return this._name;
        }
        set name(value){
            this._name = value;
        }
        get age(){
            return this._age;
        }
        set age(value){
            if(value >= 0){
                this._age = value
            }
        }
    }

    const per = new Person('孙悟空', 18);

    /*
    * 现在属性是在对象中设置的,属性可以任意的被修改,
    *   属性可以任意被修改将会导致对象中的数据变得非常不安全
    * */
    // per.setName('猪八戒');
    // per.setAge(-33);
    per.name = '猪八戒';
    per.age = -33;
    // console.log(per);
    class A{
        //protected是保护的属性,只能在当前类和子类中设置
        //protected只能在当前类和当前类的子类中设置
        protected num: number;
        constructor(num: number) {
            this.num = num;
        }
    }
    class B extends A{
        test(){
            console.log(this.num);
        }
    }
    const b = new B(123);
    // b.num = 33;
   /* class C{

        name: string;
        age: number

        // 可以直接将属性定义在构造函数中
        constructor(name: string, age: number) {
            this.name = name;
             this.age = age;
        }
    }*/
    class C{
        // 直接将属性定义在构造函数中
        constructor(public name: string, public age: number) {
        }
    }
    const c = new C('xxx', 111);
    console.log(c);
})();

9、泛型

定义一个函数或类时,有些情况下无法确定其中要使用的具体类型(返回值、属性、参数的类型不能确定),此时泛型便能通够发挥作用

  • function test(arg:any):any{
    	return arg
    }
    
  • 在这个例子中,test函数又一个参数类型不确定,但是能确定的是其返回值的类型和参数的类型是相同的,由于类型不确定所有参数和返回值均使用了any,但是很明显这样做是不合适的,首先使用any会关闭TS的类型检查,其次这样设置也不能体现出参数和返回值是相同的类型

  • 通过泛型来确认参数和返回值的类型相同

    • function test<T>(arg:T):T{
      	return arg
      }
      
    • 这里的<T>就是泛型,T是我们给这个类型起的名字(不一定必须叫T),设置泛型后即可在函数中使用T来表示该类型。所以泛型其实很好理解,就表示某个类型

    • 那么如何使用上面的函数呢?

      • 方式一(直接使用)

      • test(10)//直接使用
        
      • 使用时直接传递参数使用,类型会由TS自动推断出来,但有时编译器无法自动判断时还需要使用下面的方式

      • 方式二(指定类型)

      • 也可以在函数后手动指定泛型

    • 可以同时指定多个泛型,泛型间使用逗号隔开

      • function test<T,K>(a:T,B:K):K {
        	return b
        }
        test<number,string>(10,"hello")
        
      • 使用泛型时,完全可以将泛型当成是一个普通的类去使用

    • 类中同样可以使用泛型

      • class MyClass<T>{
        	prop:T
        	constructor(prop:T){
        		this.prop = prop
        	}
        }
        
    • 除此之外,也可以对泛型的范围进行约束

      • interface MyInter{
            length: number;
        }
        
        function test<T extends MyInter>(arg: T): number{
            return arg.length;
        }
        
      • 使用T extends MyInter表示泛型T必须是MyInter的子类,不一定非要使用接口类和抽象类同样适用。

上一篇:泛型


下一篇:实例_ Java中的代理模式