41 Typescript面向对象

简介

程序之中所有的操作都需要通过对象来完成

举例来说:

  • 操作浏览器要使用window对象
  • 操作网页要使用document对象
  • 操作控制台要使用console对象

一切操作都要通过对象,也就是所谓的面向对象,那么对象到底是什么呢?这就要先说到程序是什么,计算机程序的本质就是对现实事物的抽象,抽象的反义词是具体,比如:照片是对一个具体的人的抽象,汽车模型是对具体汽车的抽象等等。程序也是对事物的抽象,在程序中我们可以表示一个人、一条狗、一把枪、一颗子弹等等所有的事物。一个事物到了程序中就变成了一个对象。

在程序中所有的对象都被分成了两个部分数据和功能,以人为例,人的姓名、性别、年龄、身高、体重等属于数据,人可以说话、走路、吃饭、睡觉这些属于人的功能。

数据在对象中被成为属性,而功能就被称为方法。所以简而言之,在程序中一切皆是对象

面向对象的特点

封装

对象实质上就是属性和方法的容器,它的主要作用就是存储属性和方法,这就是所谓的封装

继承

继承可以将其他类中的属性和方法引入到当前类中

类class

要创建对象,必须要先定义类,所谓的类可以理解为对象的模型,程序中可以根据类创建指定类型的对象,举例来说:可以通过Person类来创建人的对象,通过Dog类创建狗的对象,通过Car类来创建汽车的对象,不同的类可以用来创建不同的对象。

定义类--语法

class 类名 {
	属性名: 类型;

	constructor(参数: 类型){
		this.属性名 = 参数;
	}

	方法名(){
		....
	}
}

属性

类属性可以分为2类:实例属性和类属性

实例属性

直接定义的属性就是实例属性,必须通过类的实例对象去访问

<script lang="ts" setup>
class Person {
  // 定义实例属性:要想访问实例属性,必须创建一个实例对象
  name: string = '初始姓名';  # 简写为 name="初始姓名"
  age: number = 18; # 简写为:age=18
}

// 创建一个实例对象
const p = new Person();
console.log(p.name, p.age);
</script>

类属性

静态属性:也称为类属性,使用静态属性无需创建实例 ,通过类即可直接使用,但是需要在属性的前面使用static关键字定义
声明静态属性后,实例对象就不可以访问到这个类属性啦

<script lang="ts" setup>
class Person {
  // 定义实例属性
  name: string = '初始姓名';

  // 定义类属性
  static age: number = 18;
}
// 创建一个实例对象
const p = new Person();
// 实例对象只能访问name,不可以访问age,因为age是类属性
console.log(p.name);

// age声明了静态属性,所以可以直接访问
console.log(Person.age);
</script>

如果是 readonly开头的属性,表示的是一个只读属性,不可以修改

// 定义类属性
readonly age: number = 18;

方法

同类属性一个,如果方法的前面添加上 static ,就是类方法

<script lang="ts" setup>
class Person {
  name: string = '初始姓名';
  age: number = 18;

  // 方法
  sayHello() {
    console.log('你好');
  }
}
// 创建一个实例对象
const p = new Person();
// 访问实例方法
p.sayHello();
</script>

构造函数和this

constructor 是构造函数的关键字,构造函数会在对象创建时调用,每创建一次对象,构造函数就会被调用一次
实例对象的传参主要是通过类的构造函数接收的

指向

在构造函数中,this当前对象就是当前创建的那个实例对象
在方法中,this当前对象就是当前调用这个方法的实例对象

<script lang="ts" setup>
class Person {
  name: string = '初始姓名'; // 初始化默认值
  age: number = 18;

  // 构造函数 constructor,构造函数会在对象创建实例时调用
  constructor(name1: string, age1: number) {
    this.name = name1;
    this.age = age1;
    // 在实例方法中,当前的this表示的当前创建的那个实例对象
    console.log(this, 'this对象');
  }
  
  // 方法
  sayHello() {
    # 在方法中,当前对象this就是调动方法的那个实例对象 
    console.log('你好', this.name);
  }
}
// 创建一个实例对象p1
const p1 = new Person('张三', 18);
// 创建一个实例对象p2
const p2 = new Person('李四', 28);

console.log(p1); // name=张三 age=18
console.log(p2); // name=李四 age=28
</script>

继承

通过继承可以将其他类中的属性和方法引入到当前类中

通过继承可以在不修改类的情况下实现对类的扩展,子类将会拥有父类的所有属性和方法

继承所有属性和方法

<script lang="ts" setup>
// 创建动物类
class Animal {
  name: string;
  age: number;
  helloTxt: string;

  // 构造函数
  constructor(name: string, age: number, helloTxt: string) {
    this.name = name;
    this.age = age;
    this.helloTxt = helloTxt;
  }
  // 方法
  sayHello() {
    console.log(`你好呀,我是${this.name},我今年${this.age}岁啦,我会${this.helloTxt}`);
  }
}

// 创建一个 Dog类,同时继承 Animal类
class Dog extends Animal {}

// 创建一个 Cat类,同时继承 Animal类
class Cat extends Animal {}

// 实例化Dog对象,创建一个Dog实例dog
const dog = new Dog('小黑', 5, '汪汪汪!!!');

// 实例化Dog对象,创建一个Cat实例cat
const cat = new Cat('喵喵', 3, '喵喵喵~~~');

dog.sayHello();
cat.sayHello();
</script>


重写方法

如果在子类中添加了和父类相同的方法,那么子类的方法会覆盖父类的方法,这种子类覆盖掉父类的形式,就是方法的重写

<script lang="ts" setup>
// 创建动物类
class Animal {
  name: string;
  age: number;

  // 构造函数
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
  // 方法
  sayHello() {
    console.log(`你好呀,我是${this.name},我今年${this.age}岁啦`);
  }
}

// 创建一个 Dog类,同时继承 Animal类,此时子类将拥有父类所有的属性和方法
// 同时也可以自己定义类的独有属性和方法
class Dog extends Animal {
  // 定义自己独有的属性
  run() {
    console.log(`${this.name}在跑步,我会汪汪汪~~~`);
  }
  // 如果在子类中添加了和父类相同的方法,那么子类的方法会覆盖父类的方法,这种子类覆盖掉父类的形式,就是方法的重写
  sayHello() {
    console.log('汪汪汪~~~');
  }
}

// 创建一个 Cat类,同时继承 Animal类
class Cat extends Animal {}

// 实例化Dog对象,创建一个Dog实例dog
const dog = new Dog('小黑', 5);
dog.run(); // 调用独有方法
dog.sayHello(); // 调动重写的方法

// 实例化Dog对象,创建一个Cat实例cat
const cat = new Cat('喵喵', 3);
cat.sayHello(); // 调用父类的方法
</script>

super 关键字

在子类重写方法的时候,super 就表示当前类的父类

在子类构建自己的构造函数的时候,必须要重新调用一次父类的构造函数

<script lang="ts" setup>
// 创建动物类--父类
class Animal {
  // 属性
  name: string;
  age: number;

  // 构造函数
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
  // 方法
  sayHello() {
    console.log(`你好呀,我是${this.name},我今年${this.age}岁啦`);
  }
}

// 创建一个 Dog类,继承 Animal类
class Dog extends Animal {
  // 构建自己的独有属性
  color: string;

  // 如果在子类中写了构造函数,也会对父类中的构造函数进行重写,所以在子类中必须要重新调用一次父类的构造函数
  constructor(name: string, age: number, color: string) {
  // 重新调用父类的构造函数
    super(name, age); 
    this.color = color;
  }

  // 重写父类
  sayHello() {
    // 此类的方法中 super 就表示当前类的父类,所以可以直接调用父类中所有的方法--一般不推荐这样写,而是重新重写自己的方法
    // super.sayHello();

    // 重写自己的方法
    console.log(`你好呀,我是${this.name},我今年${this.age}岁啦,我是${this.color}`);
  }
}

// 实例化Dog对象
const dog = new Dog('小黑', 5, '白色的');
dog.sayHello(); // 调动重写的方法
</script>

抽象类-abstract

抽象类是专门用来被其他类所继承的类它只能被其他类所继承不能用来创建实例,即只能被继承,不能创建一个实例

抽象类中可以定义普通方法,也可以定义抽象方法

抽象方法

  • 只能在抽象类中定义
  • 在方法的前面添加关键字 abstract
  • 一旦在抽象类中定义了抽象方法,那么继承它的子类,必须重写实现抽象方法
<script lang="ts" setup>
// 创建抽象类 abstract
abstract class Animal {
  // 属性
  name: string;

  // 构造函数
  constructor(name: string) {
    this.name = name;
  }
  // 定义一个 返回值为空的抽象方法
  // 抽象方法使用 abstract开头,没有方法体
  // 抽象方法只能定义抽象类中,而且子类必须对抽象方法进行重写
  abstract sayHello(): void;
}

// 创建一个子类,同时继承 父类
class Dog extends Animal {
  // 因为父类是一个抽象类,而且定义了抽象方法,所以在子类中,必须重写父类的抽象方法
  sayHello() {
    console.log(`我是${this.name}`);
  }
}

// 实例化Dog对象
const dog = new Dog('小黑');
dog.sayHello();
</script>

接口--interface

自定义 type

<script lang="ts" setup>
type myStudentType = {
  name: string;
  age: number;
};

let obj1: myStudentType = {
  name: '张三',
  age: 18,
};
let obj2: myStudentType = {
  name: '李四',
  age: 20,
};

console.log(obj1);
console.log(obj2);
</script>

1. 接口当做类型声明使用

接口可以用来定义类的结构:属性和方法,可以当做类型声明使用

<script lang="ts" setup>
interface myStudentType {
  name: string;
  age: number;
}

let obj1: myStudentType = {
  name: '张三',
  age: 18,
};
let obj2: myStudentType = {
  name: '李四',
  age: 20,
};

console.log(obj1);
console.log(obj2);
</script>

重名的接口会自动合并,接口可以重命名

<script lang="ts" setup>
interface myStudentType {
  name: string;
  age: number;
}

// 重名的接口会自动合并
interface myStudentType {
  gender: '男' | '女';
}

let obj: myStudentType = {
  name: '张三',
  age: 18,
  gender: '男',
};

console.log(obj);
</script>

2. 接口限制类的结构

  • 接口可以在定义中限制类的结构,不考虑实际值
  • 接口中所有的属性,不可以有实际值
  • 接口中定义的所有方法都是抽象方法,实例对象必须重载实现接口中的方法
实现接口-implements
<script lang="ts" setup>
// 定义一个接口
// 只限制类的结构
interface myInterface {
  name: string;
  age: number;
  sayHello(): void;
}

// 实现 implements 接口
// 接口中的 方法都是抽象方法,类必须重写
class MyStudent implements myInterface {
  name: string;
  age: number;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  sayHello() {
    console.log('哈哈哈');
  }
}

let s = new MyStudent('小明', 30);
console.log(s);
</script>

接口和自定义类型的区别

接⼝可以:

  1. 当⾃定义类型去使⽤;
  2. 可以限制类的结构;

⾃定义类型:

  1. 仅仅就是⾃定义类型;

接口和抽象类的区别

抽象类:

  1. 可以有普通⽅法,也可以有抽象⽅法;
  2. 使⽤ extends 关键字去继承抽象类;

接⼝:

  1. 只能有抽象⽅法;
  2. 使⽤ implements 关键字去实现接⼝

属性修饰符

默认情况下,对象的属性是可以任意的修改的,为了确保数据的安全性,在TS中可以对属性的权限进行设置

名称 属性 作用
readonly 只读属性 属性是只读的,无法修改
public 公开的 可以在类、子类、对象中访问和修改
private 私有的 只能在类内部访问和修改
protected 受保护的 只能在当前类和当前类的子类中访问和修改

公开属性--public默认

简写类的声明

<script lang="ts" setup>
class Person {
  name: string;
  age: number;

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

// 如果是使用了 public 修饰符,上面类的定义可以简写为:
class Person2 {
  constructor(public name: string, public age: number) {}
}
</script>

受保护属性--protected

只能在当前类和当前类的子类中访问和修改

<script lang="ts" setup>
class Person {
  protected name: string;
  protected age: number;

  constructor(name: string, age: number) {
    this.name = name; // 当前类可以修改
    this.age = age;
  }

  sayHello() {
    console.log(`大家好,我是${this.name}`);
  }
}

class Employee extends Person {
  constructor(name: string, age: number) {
    super(name, age);
    this.name = name; // 子类中可以修改
  }
}

const p = new Person('孙悟空', 18);
p.name = '猪八戒'; // 不能修改
</script>

私有属性--private

<script lang="ts" setup>
class Person{
    private name: string;
    private age: number;

    constructor(name: string, age: number){
        this.name = name; // 可以修改
        this.age = age;
    }

    sayHello(){
        console.log(`大家好,我是${this.name}`);
    }
}

class Employee extends Person{

    constructor(name: string, age: number){
        super(name, age);
        this.name = name; //子类中不能修改
    }
}

const p = new Person('孙悟空', 18);
p.name = '猪八戒';// 不能修改
</script>

</script>

getter 和 setter属性 存取器

  • 对于一些不希望被任意修改的属性,可以将其设置为private

  • 直接将其设置为private将导致无法再通过对象修改其中的属性

  • 我们可以在类中定义一组读取、设置属性的方法,这种对属性读取或设置的属性被称为属性的存取器

  • 读取属性的方法叫做setter方法,设置属性的方法叫做getter方法

<script lang="ts" setup>
class Person {
  // 定义一个私有属性:name---只能在类的内部访问和修改,即只能在Person中访问和修改
  private _name: string;
  private _age: number;

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

  // 定义一个getter和setter 存取器
  get name() {
    return this._name;
  }

  set name(value: string) {
    this._name = value;
  }

  // 定义一个getter和setter 存取器
  get age() {
    return this._age;
  }

  set age(value: number) {
    this._age = value;
  }
}
const p = new Person('哈哈哈', 18);
p.name = '嘿嘿嘿';
p.age = 28;
console.log(p); // 输出为:嘿嘿嘿 28
</script>

静态属性

静态属性(方法),也称为类属性。使用静态属性无需创建实例,通过类即可直接使用
静态属性(方法)使用static开头

泛型

定义一个函数或类时,无法明确具体类型(返回值、参数、属性的类型不能确定),此时可以使用泛型

泛型就是可以表示某个类型

定义一个泛型

<script lang="ts" setup>
# <T>就是泛型,T是一个泛型的名称(随意起),设置泛型后即可在函数中使用T来表示该类型
function test<T>(name: T): T {
  return name;
}

# 不指名类型,TS会⾃动推断出来
test(10)

# 指名具体的类型
test<number>(10)

</script>

泛型可以写多个

function test<T, K>(a: T, b: K): K{
 return b;
}
// 为多个泛型指定具体⾃值
test<number, string>(10, "hello");

类中使⽤泛型

<script lang="ts" setup>
class MyClass<T> {
  prop: T;
  constructor(prop: T) {
    this.prop = prop;
  }
}

let mm = new MyClass<string>('嘿嘿嘿');
</script>

对泛型的范围进⾏约束

<script lang="ts" setup>
interface Demo {
  length: number;
}
// 泛型T必须是MyInter的⼦类,即:必须拥有length属性
function test<T extends Demo>(arg: T): number {
  return arg.length;
}
test(10); // 类型“number”的参数不能赋给类型“Demo”的参数
test({ name: '张三' }); // 类型“{ name: string; }”的参数不能赋给类型“Demo”的参数
test('123');
test({ name: '张三', length: 10 });
</script>

posted @ 2024-04-13 17:13  songxia777  阅读(1)  评论(0编辑  收藏  举报