面向对象

TypeScript 是面向对象类的编程。

什么是面向对象呢?

简而言之,就是程序中所有的操作,都是通过对象来完成的。计算机程序本质是对现实事物的抽象。一个人,一辆车,一只狗,这些都是对象,而这些对象发出的动作就是对象的方法,某些特征就是对象的属性。比如一个人,他的身高,长相,姓名,年龄等这些在对象中都是他的属性,而他发出的动作,走,跑,说话等这些在对象中都是他的方法。

类是什么?

类其实就是对象的模型,通过类可以来创建对象。对象的属性和方法,要在类中明示的表示出来。

下面定义一个简单的类:

class Person {  name: string = 'xiaobai';  static age: number = 18;  say() {console.log('hello');
  }  static run() {console.log('run');
  }
}let person = new Person()复制代码

上面定义了一个简单的类,通过 new 关键字,实例化成对象。name 是实例对象。

static 指的是类的静态属性,用类名访问,无需创建对象,比如 Person.age。但是实例对象访问不到。静态方法与静态属性使用方式相同。

类的构造函数 constructor

为什么需要它呢?

当我们在创建类的时候,通常都不可能只实例化一个对象,通常会实例化多个对象。

// 这样创建出来的实例对象都是相同的属性值和方法class Person {  name: string = 'xiaobai';  say() {console.log('hello');
  }
}let person = new Person()复制代码

constructor 这个构造函数在每次 new 实例化对象,立即执行。

class Person {  name: string;  constructor(name: string) {this.name = name;console.log(this);  // 它指向的是per这个实例化的对象
  }
}let per1 = new Person('xiaobai1');let per2 = new Person('xiaobai2');let per3 = new Person('xiaobai3');复制代码

在每次实例化对象完成之后,在 constructor 函数里面会生成一个 this 这个 this 指向的就是实例化产生的对象。

通过this.name  添加属性,就相当于往实例化的对象里面添加属性。这样我们再把实例化时传的参数带进去,这样就会创建出不同属性值的对象了。方法的话,就是哪个实例对象调用方法,方法里面的this就指向谁。

类的继承 extends

class Dog {  name: string;
  age: number;  constructor(name: string, age: number) {this.name = name;this.age = age;
  }  say() {console.log('汪汪');
  }
}class Cat {  name: string;
  age: number;  constructor(name: string, age: number) {this.name = name;this.age = age;
  }  say() {console.log('喵喵');
  }
}复制代码

我们分别创建一个 Dog 和 Cat 类,我们发现它们都有相同的属性和方法,这样的代码我们重复写了两遍,如果我们再去创建一个牛,蛇,猪等更多的类,这样就开始做重复的工作了。

因此我们使用继承来简化它:

class Animal {  name: string;
  age: number;  constructor(name: string, age: number) {this.name = name;this.age = age;
  }  say() {console.log('叫');
  }
}class Dog extends Animal {  run() {console.log(`${this.name}在跑`);
  }  // 重写父类方法
  say() {console.log('汪汪')
  }
}class Cat extends Animal {  // 重写父类方法
  say() {console.log('喵喵')
  }
}let dog = new Dog('小白', 3);console.log(dog);      // {name: "小白", age: 3}dog.say();  // 汪汪let cat = new Cat('小黑', 2);console.log(cat);      // {name: "小黑", age: 2}dog.say();  // 喵喵复制代码

从上面代码可以看出,我们使用了 extends 关键字来继承 Animal 这个类,从而使 Dog , Cat 这两个类,都拥有了 Animal 类上的方法。这大大简化了我们的代码量,而且不用在去做重复的工作。

继承的子类,不仅有拥有父类的方法,还能有自己的方法,比如,子类 Dog 中的 run 方法。

如果继承的子类中添加了与父类相同的方法,子类的方法将覆盖父类的方法。这个子类方法覆盖掉父类的方法的形式,称为方法的重写。

super 关键字

先写一段代码:

class Animal {  name: string;  constructor(name: string) {this.name = name;
  }  say() {console.log('叫');
  }
}class Dog extends Animal {  age: number;  constructor(name: string, age: number) {super(name);   // 调用父类的构造函数this.age = age;
  }  say() {super.say()    // 调用父类的方法
  }
}let dog = new Dog('小白', 3);console.log(dog);复制代码

在子类的方法中使用 super 表示当前类的父类。

在子类的添加新的属性,如果在子类中写了新的构造函数,子类构造函数必须对父类构造函数进行调用。

抽象类

有的时候,我们并不需要父类来实例化创建对象,父类是一个超类,只用来存放公用的属性和方法,用来继承使用。这个时候就用到了抽象类,抽象类只能用来继承,而不能用来实例化创建对象。

abstract class Animal {  name: string;  constructor(name: string) {this.name = name;
  }  abstract say(): void;
}class Dog extends Animal {  say() {console.log('叫');
  }
}复制代码

在 abstract 抽象类中,才能定义抽象方法,子类必须对抽象方法进行重写。抽象方法使用 abstract 开头,没有方法体。抽象类中,可以不写抽象方法。

类的修饰符

类的修饰符 有三个 public  private  protected

public

公共属性,修饰的属性可以在任意位置进行访问(修改),包括继承的子类

属性前不设置修饰符时 默认为 public

class Person {  public name: string;  public age: number;  constructor(name: string, age: number) {this.name = name;this.age = age;
  }
}let person = new Person('孙悟空', 18);console.log(person);    // 输出 {name: "孙悟空", age: 18}person.name = '猪八戒';
person.age = 20;console.log(person);    // 此时输出 {name: "猪八戒", age: 20}复制代码

此时可以看出使用 public 修饰属性:

属性可以在实例化对象中任意被修改,这样使对象中的数据变得非常不安全。

private

私有属性,只能在类内部进行访问(修改)

此时我们将 Person 类中的,name属性改成 私有属性,结果如下:

class Person {  private name: string;  public age: number;  constructor(name: string, age: number) {this.name = name;this.age = age;
  }
}let person = new Person('孙悟空', 18);console.log(person);    // 输出 {name: "孙悟空", age: 18}person.name = '猪八戒';  // 此时抛出错误,“属性‘name’为私有属性,只能在 Person 类中访问”复制代码

这样属性被私有化,外部就不能 以对象点属性的方法去访问了,那么我们可以通过 向类中添加方法去访问(修改)里面的属性,如下:

class Person {  private name: string;  public age: number;  constructor(name: string, age: number) {this.name = name;this.age = age;
  }

  getName () {return this.name;
  }

  setName (value: string) {this.name = value;
  }
}let person = new Person('孙悟空', 18);
person.setName('猪八戒');console.log(person.getName());   // 打印出 '猪八戒'复制代码

可以通过设置 setName 方法,在对象内部改变去修改 name 这个私有属性。

当然也可以直接直接使用 TS 提供的 getter , setter 方法,代码如下:

class Person {  private name: string;  public age: number;  constructor(name: string, age: number) {this.name = name;this.age = age;
  }  get _name() {return this.name;
  }  set _name(value: string) {this.name = value;
  }
}let person = new Person('孙悟空', 18);
person._name = '猪八戒';console.log(person._name);   // '猪八戒'复制代码

用private可以做属性的封装,不直接去访问它的属性,通过 getter,setter 方法去访问。

protected

受保护的属性,只能在当前类和子类中使用(修改),不能在实例化的对象中去访问。

接口

接口实现对象

接口用来定义一个类结构,用来定义一个类中应该哪些属性和方法,同时接口也可以当做类型声明去使用。

举个例子:

interface User {  name: string;
  age: number;
}interface User {  gender: string;
}const USER1: User = {  name: '小明',  age: 18,  gender: '男'}console.log(USER1);复制代码

我们定义一个 user 对象的接口,声明的第一个接口中包括,name 和 number 属性,声明第二个接口包含 gender 属性,我们可以看到两个接口可以重复声明,并且两个接口中的属性检测会合并,我们再去使用这个接口时,就必须有 name  age  gender。

接口实现类

接口可以在定义类的时候去限制类的解构

接口定义类的特点:

  • 接口中所有的属性都不能有实际值
  • 接口只定义对象结构,而不考虑实际值
  • 在接口中所有的方法都是抽象方法

定义类时,可以使类去实现一个接口,实现接口就是使类满足接口的要求

/**
 * 接口实现类
 */interface user {  name: string;
  say(): void;
}class user1 implements user {  name: string;  constructor(name: string) {this.name = name;
  }  say() {console.log('hello');
  }
}复制代码

由此可以看出,接口很像抽象类,但是也有很显著的区别:

  • 在接口中的方法都是抽象方法,不能有实际的值,在抽象方法中可以有抽象方法,也可以有普通方法
  • 在使用接口的时候,使用关键字 implements 实现,在使用抽象类的时候,使用 extends 继承

本质上两者都是对类定义一个标准,使实现接口,或者继承抽象方法的类,必须去符合这个标准。

泛型

在定义函数或者类时,如果遇到类型不明确的就可以使用泛型。

当我们不明确函数传参是什么类型的时候,往往会使用 any 来表示任意类型,但是使用 any ,会关闭 TS 的类型检测,因此这里要用泛型来代替。

举个简单的代码案例:

// 使用any,关闭类型检测function fn(a: any): any {   return a;
}// 使用泛型,自动判断类型function fn<T> (a: T): T {  return a;
}
fn<string>('hello');  // 也可以在调用的时候,自己指定类型复制代码

类使用泛型:

class User<T> {  name: T;  constructor(name: T) {this.name = name;
  }
}let user = new User<string>('zhangsan');复制代码