面向对象简介
面向对象是程序设计语言中一个非常重要的思想,初次接触是在学习java时,当时对于面向对象的学习没有很精通,索性借着学习ts的机会认真的研究一下。
所谓面向对象,首先将对象当做一个名词去理解,面向着他开发,程序之中的所有操作都需要通过对象去完成,举个例子,在前端开发中的:
- 操作浏览器需要借助window对象
- 操作网页需要使用document对象
- 操作控制台需要借助console对象
那么对象是什么呢?可以说:世间万物都是对象。像是一个人,一只猫咪,一颗子弹等等,都可以抽象成程序中的对象。程序中所有的对象都被分为两个部分数据和功能,以一个人为例,他的身高、体重、姓名、年龄等属于数据,人可以说话、走路、吃饭,这些属于人的功能。数据在对象中被称为属性,功能则被称为方法
类(class)
了解了对象之后,我们还要知道在程序中如何创建对象。要想创建对象,必须要先定义类,所谓类(class),可以理解为对象的模型,程序中可以使用类传递不同的参数创建特定的对象
语法:
class 类名{
属性名: 类型
...
constructor(参数1: 类型, 参数2: 类型...){
this.属性名 = 参数
...
}
方法名(){
/*...*/
}
}
举例:定义一个Person类
class Person{
name: string;
age: number;
constructor(name: string, age: number){
this.name = name
this.age = age
}
walk(){
console.log(this.name + '在走路')
}
}
const person = new Person('wmh', 18) //实例一个Person对象
注:可以直接将属性定义在构造函数中
关于属性:
- 直接定义的属性是实例属性,必须通过对象的实例访问
person.name = 'wyz'
- 使用static关键字修饰,表明声明一个静态属性(类属性),类属性需要直接使用类来调用
console.log(Person.静态属性名)
- 使用readonly关键字修饰,表明声明一个只读属性,只能进行读操作
- 使用public关键字修饰,默认值,表示可以在任意地方访问
- 使用private关键字修饰,私有属性,只能在类内部进行访问
- 使用protected关键字修饰,只能在当前类和当前类的子类中访问
继承与多态
1. 继承
- 对于继承这个概念,用例子来说明,假设在一个程序中需要用到Dog和Cat两个类,从构成来看,这两个类有许多部分可以共用,单独定义的话就会造成代码的冗余,此时我们就可以将这两个类的公共部分提取出来,定义一个Animal类,让Dog和Cat类继承Animal类所有的属性和方法,特定的属性方法单独定义,这就是继承。
// Animal类
class Animal{
name: string;
age: number;
constructor(name: string, age: number){
this.name = name;
this.age = age;
}
bark(){
console.log('动物在叫~~')
}
}
// Dog类
class Dog extends Animal{
bark(){
console.log(`${this.name}在汪汪叫!`);
}
}
// Cat类
class Cat extends Animal{}
const dog = new Dog('旺财', 4);
const cat = new Cat('小金', 5)
dog.bark();
- 此时Dog和Cat类也成为子类、派生类,Animal类被称为父类、超类
- 如果在子类中添加了和父类同名的方法,则子类会覆盖掉父类的方法,这种形式,我们称为方法重写
- 在子类的方法中,super就表示当前子类的父类,如果在子类中写了构造函数,则需要在子类的构造函数中调用super(参数),表明调用父类的构造函数
2. 多态
参考知乎: https://www.zhihu.com/question/30082151.
花木兰替父从军(摘自知乎)
抽象类与接口
1. 抽象类
- 以abstract修饰的类被称为抽象类,与一般的类相比,抽象类不能用来创建实例对象,只能被其他的类继承,抽象类中可以定义抽象方法
- 抽象方法同样以abstract修饰,不含方法体,只能定义在抽象类中,抽象类的子类必须重写抽象方法
abstract class Person{
abstract sayHello(): void; //抽象方法
}
2. 接口
- 通俗的来说,接口就是用来定义一个类的结构,限制一个类中需要包含的属性和方法
- 在定义类的时候使用implements关键字实现接口
- 实例
interface Person{
name: string;
sayHello():void;
}
class Student implements Person{
constructor(public name: string) {
}
sayHello() {
console.log('大家好,我是'+this.name);
}
}
3. 抽象类与接口的对比
- 抽象类中的方法可以是抽象方法,也可以不是;接口中的属性方法都只能是抽象的,不能有实际值,它只是用来限制结构
- 在同一作用域中,抽象类不可以重名,但接口可以,他会合并重名接口中的属性方法
- 一个类只能继承至一个抽象类,但可以实现多个接口
属性的封装
- 由于常规的定义方式会使得类的属性暴露在外,不建议这么做,一般会采用对类的属性进行访问的限制,private或protected修饰,再配合类内部的getter和setter方法对属性进行访问
- 实例:
class Person{
private _name: string;
constructor(name: string){
this._name = name;
}
get name(){
return this._name;
}
set name(name: string){
this._name = name;
}
}
const p1 = new Person('孙悟空');
console.log(p1.name); // 通过getter读取name属性
p1.name = '猪八戒'; // 通过setter修改name属性
泛型
- 定义一个函数或是一个类时,有时候会出现无法确定类型的情况,包括参数、属性、返回值的类型,此时泛型便可以发挥作用
- 举个例子:
function test(arg: any): any{
return arg;
}
上例中,test函数的参数类型无法确定,但可以确定参数和返回值类型相同,由于类型不确定所以使用any类型,但这样明显是不合适的,首先any会关闭ts的类型检查,其次这样也无法体现参数和返回值是相同类型
- 这里我们就可以使用泛型
function test<T>(arg: T): T{
return arg;
}
这里的<T>就是一个泛型,T是这个泛型的名字(自定义),定义之后即可在函数或类中使用这个泛型,泛型其实就表示某个类型
对于上述的函数,有两种使用方式
- 方式一(直接使用):视作普通函数
test(10)
- 方式二(指定类型):在函数后制定泛型的类型
test<string>('wmh')
另外,可以同时指定多个泛型,泛型间使用逗号隔开
function test<T, K>(a: T, b: K): K{
const x = a
return b;
}
test<number, string>(10, "hello");
在类中也可以使用泛型:
class MyClass<T>{
prop: T;
constructor(prop: T){
this.prop = prop;
}
}