TS_面向对象

本文详细介绍了 TypeScript 中的面向对象编程概念,包括类、属性、方法、构造函数、this、继承、重写、super、抽象类和接口。讲解了如何使用构造函数初始化对象,使用this关键字,以及如何通过继承和接口实现代码复用和结构规范。此外,还讨论了属性的封装和存取器的使用,以及泛型在函数和类中的应用。
摘要由CSDN通过智能技术生成

面向对象的思想

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

  • 那么什么是对象呢?
    程序是对现实事物的抽象,而一个事物到程序中就变成了一个对象。
    如在学生管理系统中一个学生就是一个对象。

  • 对象的两个内容
    一个对象可以包含属性和方法。属性即一个对象的静态数据,如姓名、年龄;方法即一个对象的动态行为,如走路、游泳。

  • 在程序中一切皆是对象

类简介

类相当于对象的模型,我们通过类创建对象。即类的实例化即对象。
类的定义包括两个方面:属性和方法

属性

属性的定义和变量的定义方法一样。

  • 实例属性:直接定义
    直接定义的属性是实例属性,需要通过类的实例对象去访问;
  • 静态属性
    使用static开头的属性是静态属性(类属性),需要直接通过类进行访问。对象不可访问。
// 使用class定义类

class Person{
    // 定义属性(实例属性)
    name: string='yang'

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

// 使用类创建对象
const pre = new Person()

console.log(pre.name)
console.log(Person.age)

输出:
在这里插入图片描述

  • 只读属性
    readonly开头的属性表示: 一个只读的属性,无法修改
    eg:
class Person{
    // 定义属性(实例属性)
    readonly name: string='yang'

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

方法

方法的定义格式

方法名(){
}
  • 实例方法:直接定义的方法,需要通过类的实例对象去访问;
  • 静态方法:使用static开头的属性是静态方法(类属性),需要直接通过类进行访问。
    eg:
// 使用class定义类

class Person{
    name: string = 'yang'
    // 实例方法
    sayHello() {
        console.log("hello")
    }

    // 静态方法
    static sayGood() {
        console.log("good")
    }
}

// 使用类创建对象
const pre = new Person()

pre.sayHello()
Person.sayGood()

在这里插入图片描述

构造函数

构造函数的作用是创建对象,通过类的构造函数来创建对象。

  • 构造函数与类的名字相同
  • 不显示创建构造函数时,有一个默认的构造函数
  • 显示构造函数的创建:
    格式:
    // 构造函数,在对象创建时调用
    constructor(参数名:属性类型,参数名:属性类型) {
    }

在构造函数中,this表示当前正在创建的对象,可以通过this为新建的对象设置属性
eg:

class Dog{
    name: string;
    age: number;
    // 构造函数,在对象创建时调用
    constructor(name:string,age:number) {
        this.name = name,
        this.age=age
    }

    // 方法
    bark() {
        alert("汪汪!")
    }
}
const dog1 = new Dog("小米", 3)
const dog2 = new Dog("小白", 4)
console.log(dog1)
console.log(dog2)

输出:
在这里插入图片描述

  • 如果显式声明了构造函数,系统就不会提供默认的无参构造函数了
  • 构造函数可以有多个

构造函数的简写

可以直接将属性定义在构造函数中:

class C {
    constructor(public name:string,public age:number) {
    }
}

等价于

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

this

在实例方法中,this表示当前实例对象
在构造函数中,this表示当前正在创建的对象
eg:在实例方法中使用this

class Dog{
    name: string;
    age: number;
    // 构造函数,在对象创建时调用
    constructor(name:string,age:number) {
        this.name = name,
        this.age=age
    }

    // 方法
    bark() {
        console.log(this.name+":汪汪!")
    }

}
const dog1 = new Dog("小米", 3)
const dog2 = new Dog("小白", 4)
dog1.bark()
dog2.bark()

输出:
在这里插入图片描述

继承

格式: 类1 extends 类2
此时,类2 被称为父类,类1 被称为子类。使用继承后,子类将会拥有父类所有的方法和属性。
通过继承可以将多个类中共有的代码写在一个父类中,
eg:

(function () {
    class Animal{
        name: string;
        age: number;
        constructor(name: string, age: number) {
            this.name = name;
            this.age = age;
        }
        sayHello() {
            console.log("动物叫")
        }
    }

    // 
    
    class Dog extends Animal{
    }
    
    class Cat extends Animal{
    }

    const dog = new Dog('小米', 3)
    const cat = new Cat("泡芙", 2)
    dog.sayHello()
    cat.sayHello()

})()

在这里插入图片描述
这样就完成了继承,但是这样写显然是没有意义的。继承是为了将公有的部分提取出来,同时保留自己私有的部分来简化代码的编写。如果子类没有自己的属性或方法,就无需继承,直接使用父类即可。

所以我们为Dog和Cat添加自己的属性和方法。

(function () {
    class Animal{
        name: string;
        age: number;
        constructor(name: string, age: number) {
            this.name = name;
            this.age = age;
        }
        sayHello() {
            console.log("动物叫")
        }
    }
    
    class Dog extends Animal{
        run() {
            console.log(`${this.name}在跑`)
        }
    }
    
    class Cat extends Animal{
        color: string = "white";
        sleep() {
            console.log(`${this.name}在睡觉`)
        }
    }

    const dog = new Dog('小米', 3)
    const cat = new Cat("泡芙", 2)
    dog.sayHello()
    cat.sayHello()
    dog.run()
    cat.sleep()
    console.log(cat.color)
})()

在这里插入图片描述

重写

同时我们可以在子类中定义与父类同名的方法,并且子类的方法会覆盖父类的方法,这就是方法的重写。

(function () {
    class Animal{
        name: string;
        age: number;
        constructor(name: string, age: number) {
            this.name = name;
            this.age = age;
        }
        sayHello() {
            console.log("动物叫")
        }
    }
    
    class Dog extends Animal{
        run() {
            console.log(`${this.name}在跑`)
        }
        sayHello() {
            console.log("汪汪汪!")
        }
    }
    
    class Cat extends Animal{
        color: string = "white";
        sleep() {
            console.log(`${this.name}在睡觉`)
        }
        sayHello() {
            console.log("喵喵喵~")
        }
    }

    const dog = new Dog('小米', 3)
    const cat = new Cat("泡芙", 2)
    dog.sayHello()
    cat.sayHello()
    dog.run()
    cat.sleep()
    console.log(cat.color)
})()

在这里插入图片描述

super

super关键字代表父类。
super():调用父类的构造方法。
super.属性/方法:调用父类的属性或方法。

  • 如果子类中声明了构造函数,一定要调用父类的构造函数即super()
(function () {
    class Animal{
        name: string;
        age: number;
        constructor(name: string, age: number) {
            this.name = name;
            this.age = age;
        }
        sayHello() {
            console.log("动物叫")
        }
    }
    
    class Dog extends Animal{
        run() {
            console.log(`${this.name}在跑`)
        }
        sayHello() {
            console.log("汪汪汪!")
        }
    }
    
    class Cat extends Animal{
        color: string;
        constructor(name: string, age: number, color: string) {
            super(name, age)
            this.color = color
        }
        sleep() {
            console.log(`${this.name}在睡觉`)
        }
        sayHello() {
            console.log("喵喵喵~")
        }
    }

    const dog = new Dog('小米', 3)
    const cat = new Cat("泡芙", 2,"white")
    dog.sayHello()
    cat.sayHello()
    dog.run()
    cat.sleep()
    console.log(cat.color)
})()

在这里插入图片描述

抽象类

上述例子我们不希望父类被用来创建对象,只通过Dog和Cat来创建对象好了。
这就可以通过将父类设置为抽象类即可。
格式 :abstract class 类名{}

抽象类的特点:

  • 抽象类不能被实例化,是专门用来被继承的类
  • 抽象类中可以添加抽象方法
    抽象方法:定义格式:abstract 方法名():返回值类型;
    抽象方法的特点:
    - 抽象方法使用abstract开头,没有方法体
    - 抽象方法只能定义在抽象类
    - 子类必须对抽象方法进行重写

eg:

(function () {
    abstract class Animal{
        name: string;
        age: number;
        constructor(name: string, age: number) {
            this.name = name;
            this.age = age;
        }
        abstract sayHello():void;
    }
    
    class Dog extends Animal{
        sayHello() {
            console.log("汪汪汪!")
        }
    }
    
    const dog = new Dog('小米', 3)

})()

接口

接口定义类的结构

  • 接口用来定义一个类的结构,用来定义一个类中应该用有哪些属性和方法。
  • 接口中的所有的属性都不能有实际的值,接口只定义对象的结构,而不考虑实际值,即在接口中所有的方法都是抽象方法(因为都是抽象方法也无需声明abstract关键字了)。
  • 接口有点类似于抽象类。
    但是区别在于接口中不能定义实际值,方法都是抽象的;抽象类中可以定义实际值,方法可以定义为抽象的,也可不定义为抽象的。

我们说接口用来定义一个类的结构,是因为接口一般由类进行实现。实现接口就是使类满足接口的要求。
实现格式:class 类名 implements 接口名

eg:

(function () {

    interface myInterface{
        name: string;
        age: number;
        sayHello(): void;
    }
    class MyClass implements myInterface{
        name: string;
        age: number;
        constructor(name: string, age: number) {
            this.name = name;
            this.age = age
        }
        sayHello(){
            console.log("hello")
        }

    }
    
})()
  • 接口的作用:只要实现了接口就可以使用相应的功能。

接口定义类型声明

  • 同时接口也可以当成类型声明去使用
    eg:
(function () {
    type myType = {
        name: string;
        age:number
    }

    interface myInterface{
        name: string;
        age:number
    }

    const obj1: myType = {
        name: "yang",
        age:18
    }
    const obj2: myInterface = {
        name: 'cheng',
        age:20
    }
})()
  • 比自定义类型的优点是,可以定义多个重名接口,(最后会将这些重名接口合并成一个接口)
(function () {
    type myType = {
        name: string;
        age:number
    }

    interface myInterface{
        name: string;
        age:number
    }
    interface myInterface{
        gender:string
    }


    const obj1: myType = {
        name: "yang",
        age:18
    }
    const obj2: myInterface = {
        name: 'cheng',
        age: 20,
        gender:"male"
    }
})()

属性的封装

问题产生,属性封装

属性任意被修改将会导致对象中的数据变得非常不安全,所以对象的属性一般不应该直接能被修改。

属性可以被任意修改是因为属性默认是全局可见的public的。
有3种修饰符:

  • public修饰的属性可以在任意位置访问(修改)默认值
  • private私有属性,私有属性只能在类内部进行访问(修改)
    (private属性不可以继承给子类)
  • protected受保护的属性,只能在当前类和当前类的子类中访问

所以我们可以把属性设置为私有的private,就不能随便修改值了

存取器的设置

  • 但是我们该如何读取该属性?
    通过设置方法:
get属性名() {
            return this.属性名;
        }
  • 那如果我们想修改该属性?
    也是通过设置方法:
 set属性名(属性名: string) {
            this.属性名 =属性名
        }

这样设置下来属性也是可读可写的,和public有什么区别呢?
区别就在于此时属性的可读和可写的控制权在我们自己,你不想让该属性被访问就不设置set方法,你不想让该属性改变就不设置get方法。
同时使用set方法还可以避免一些错误值的赋值:

(function () {
    class Person{
        private name: string;
        private age: number;
        // 构造函数,在对象创建时调用
        constructor(name:string,age:number) {
            this.name = name,
            this.age=age
        }
        getName() {
            return this.name;
        }
        setName(name: string) {
            this.name =name
        }
        getAge() {
            return this.age;
        }
        setAge(age: number) {
            if (age >= 0) {
                this.age =age
            }
        }
    }
    
    const per1 = new Person("yang", 18)
    per1.setName('cheng')
    per1.setAge(-10)
    console.log(per1)
    
})()

在这里插入图片描述

存取器的定义

所以我蛮一般情况下使用:

  • getter方法用来读取属性
  • setter方法用来设置属性

它们被称为属性的存取器

存取取的快速生成:

ts中的getter和setter方法

设置getter和setter方法后,就不可以直接通过.来获取属性了,感觉有点麻烦,于是ts设置了getset

(function () {
    class Person{
        private _name: string;
        private _age: number;
        // 构造函数,在对象创建时调用
        constructor(name:string,age:number) {
            this._name = name,
            this._age=age
        }
        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("yang", 18)
    console.log(per.name)
    per.name = 'cheng'
    console.log(per.name)
    
})()

泛型

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

  • 函数定义泛型
    eg:
function fn<T>(a: T): T{
    return a
}

T就表示我们定义的泛型(名字自己定义即可)

  • 调用泛型函数
    不指定泛型,TS可以自动对类型进行推断,但是不是所有类型都可以指定出来
    指定泛型,函数名<类型名>(参数)
function fn<T>(a: T): T{
    return a
}

fn(10)//不指定泛型,TS可以自动对类型进行推断,在这里T就是number
fn<string>("hello")//指定泛型
  • 可以使用多个泛型
function fn2<T, K>(a: T, b: K): T{
    console.log(b)
    return a
}

fn2(10,12)//不指定泛型,TS可以自动对类型进行推断,在这里T就是number
fn2<number,string>(12,"hello")//指定泛型
  • 可以为泛型指定大致范围(使用接口或类)
//T extends Inter 表示泛型T必须时Inter实现类(子类)
interface Inter{
    length:number
}
function fn3<T extends Inter>(a: T): number{
    return a.length
}

fn3({length:10})
  • 在类中使用泛型
class myClass<T>{
    name: T;
    constructor(name: T) {
        this.name = name
    }
}
const my = new myClass<string>('yang')
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值