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>
接口和自定义类型的区别
接⼝可以:
- 当⾃定义类型去使⽤;
- 可以限制类的结构;
⾃定义类型:
- 仅仅就是⾃定义类型;
接口和抽象类的区别
抽象类:
- 可以有普通⽅法,也可以有抽象⽅法;
- 使⽤ extends 关键字去继承抽象类;
接⼝:
- 只能有抽象⽅法;
- 使⽤ 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>