一篇文章带你掌握TypeScript中的类和对象

前言

TypeScript 由 Microsoft 开发,是一种开源的、面向对象的编程语言。当然,它是 JavaScript 的一个强大的语法超集,并改进了该语言的可选静态类型。其中最重要的一点就是TypeScript 支持面向对象的所有特性。

在JavaSript中,可以说一切皆为对象,当我们用面向对象的思想进行封装代码时,不论是用构造函数,对象,还是ES6新增的class时,其实本质上都是在使用JavaScript的Object对象,这就要求在进行一些比较复杂的对象封装时,需比较熟悉对象的原型链等原理,才能封装出简洁,易读,易扩展的对象。面向对象的三大特征为继承、封装、多态。JavaScript虽然可以模拟实现继承和封装,但无法良好的模拟实现多态。

ES5实现类代码示例:

// 定义一个Person类
function Person(name, age) {
    this.name = name; // 公有属性
    var privateAge = age; // 私有属性(通过闭包保存)
    
    // 获取年龄的公共方法
    this.getAge = function() {
        return privateAge;
    };
}
 
// 创建一个新的Person对象
var person1 = new Person("张三", 20);
console.log(person1.name); // 输出 "张三"
console.log(person1.age); // undefined (无法直接访问私有属性)
console.log(person1.getAge()); // 输出 20 (调用公共方法获取私有属性值)

ES6实现类代码示例:

class Person {
    // 构造函数
    constructor(name, age) {
        this.name = name
        this.age = age
    }

    // 方法
    say(){
        console.log('我能说话')
    }
}
// 实例化
let zs = new Person('张三', 24)
// 实例化
let ls = new Person('李四', 24)
console.log(zs)
console.log(ls)

TS实现类代码示例:

class Person{
    public name:string;
    private age:number;

    constructor(name:string,age:number){
        this.name = name;
        this.age = age;
    }
    public setAge(age:number){
        this.age = age;
    }
    public getAge():number{
        return this.age;
    }
}

let person = new Person("xiaoming",20);
console.log(person.name);
console.log(person.getAge());

ES6引入了类的概念,为 JavaScript 增加了面向对象编程的能力。ES6 中的类是一种语法糖,本质上仍然是基于原型的继承。使用类可以定义构造函数、实例方法和静态方法,并且支持继承和类之间的关系。TypeScript新增了类、接口、泛形等等这些特性。相比“灵活自由”的JavaScript式封装,运用这些特性将会使初学者写的代码更加规范,更易于阅读和扩展,降低了一些封装的门槛,同时也方便了一些熟悉面向对象编程的开发者进行开发,TypeScript相比ES6具有更强的类型系统和其他功能 ,并且而对于大型的项目开发,这种面向对象的开发方式能有助于团队合作,提高开发人员的工作效率。

类的特性

传统方法中,JavaScript 通过构造函数实现类的概念,通过原型链实现继承。而在 ES6 中,我们终于迎来了class。TypeScript 除了实现了所有 ES6 中的类的功能以外,还添加了一些新的用法。这一节主要介绍TypeScript中类的用法。

 虽然 JavaScript 中有类的概念,但是可能大多数 JavaScript 程序员并不是非常熟悉类,对于学过Java等面向对象的小伙伴来说,学习TypeScript是非常轻松的,接下来,我就从一下常见属性来讲解TypeScript面向对象特性。

类(Class):定义了一件事物的抽象特点,包含它的属性和方法

对象(Object):类的实例,通过 new 关键字生成

面向对象(OOP)的三大特性:封装、继承、多态

封装(Encapsulation):将对数据的操作细节隐藏起来,只暴露对外的接口。外界调用端不需要(也不可能)知道细节,就能通过对外提供的接口来访问该对象,同时也保证了外界无法任意更改对象内部的数据

继承(Inheritance):子类继承父类,子类除了拥有父类的所有特性外,还有一些更具体的特性

多态(Polymorphism):由继承而产生了相关的不同的类,对同一个方法可以有不同的响应。比如 Cat 和 Dog 都继承自 Animal,但是分别实现了自己的 eat 方法。此时针对某一个实例,我们无需了解它是 Cat 还是 Dog,就可以直接调用 eat 方法,程序会自动判断出来应该如何执行 eat

存取器(getter & setter):用以改变属性的读取和赋值行为

修饰符(Modifiers):修饰符是一些关键字,用于限定成员或类型的性质。比如public表示公有属性或方法

抽象类(Abstract Class):抽象类是供其他类继承的基类,抽象类不允许被实例化。抽象类中的抽象方法必须在子类中被实现

接口(Interfaces):不同类之间公有的属性或方法,可以抽象成一个接口。接口可以被类实现(implements)。一个类只能继承自另一个类,但是可以实现多个接口

 

1. TypeScript中的类和类的实例化

定义类的关键字为 class,后面紧跟类名,类可以包含以下几个模块(类的数据成员): 

字段 : 字段是类里面声明的变量。字段表示对象的有关数据。

构造函数 :类实例化时调用,可以为类的对象分配内存。

方法 : 方法为对象要执行的操作。

class {

        [修饰符] 字段名:[字段类型];

        constructor([参数1:类型,....]){

                // 执行一些初始化操作

        },

       [修饰符] 方法名([参数1:类型,....):返回值类型{

                // 方法体....

        }

}

例如:

class Person{
    public name:string;
    private age:number;

    constructor(name:string,age:number){
        this.name = name;
        this.age = age;
    }
    public setAge(age:number){
        this.age = age;
    }
    public getAge():number{
        return this.age;
    }
}

let person = new Person("xiaoming",20);
console.log(person.name);
console.log(person.getAge());

2. 类的继承,以及类继承后super的使用

class User{
    private name:string;

    constructor(name:string){
        this.name = name;
    }
    public setName(name:string):void{
        this.name = name;
    }
    public getName():string{
        return this.name;
    }
}

class Admin extends User{
    private password:string;

    constructor(name:string,password:string){
        super(name);
        this.password = password;
    }


    public setPassword(password:string){
        this.password = password;
    }
    public getPassword():string{
        return this.password;
    }
}

let user1 = new User("xujingliang");
console.log(user1.getName());

let admin = new Admin("xujingliang","123456")
console.log(admin.getName());
console.log(admin.getPassword());

3. 类继承后的方法重写

类继承后,子类可以对父类的方法重新定义,这个过程称之为方法的重写。

重写是子类继承父类之后,方法名称,参数名称,返回值类型全部相同只能修改方法体中的相关操作。

重写的优点:在拥有父类方法特征的同时,自身也可以有一些特有的特征

class User{
    private name:string;
    constructor(name:string){
        this.name = name;
    }
    public setName(name:string):void{
        this.name = name;
    }
    public getName():string{
        return this.name;
    }
    public say():string{
        return "My name is "+this.name;
    }
}

class Admin extends User{
    private password:string;

    constructor(name:string,password:string){
        super(name);
        this.password = password;
    }


    public setPassword(password:string){
        this.password = password;
    }
    public getPassword():string{
        return this.password;
    }
    public say():string{
        return "My name is "+super.getName()+" and my password is "+this.password;
    }
}

let user = new User("xujingliang");
console.log(user.say());

let admin = new Admin("xujingliang","123456")
console.log(admin.say());

上面的代码示例中,我们先定义了一个User用户类,又定义了一个Admin管理员类。Admin类继承User类,并对User类的say()方法进行重写。 

4. static关键字的使用

使用static生命的类字段或者方法不需要实例化对象,可以直接通过类名点字段名称或者类名点方法名称直接调用。

class Person{
    public name:string;
    private age:number;
    static mark:string = "普通用户"
    constructor(name:string,age:number){
        this.name = name;
        this.age = age;
    }
    public setAge(age:number){
        this.age = age;
    }
    public getAge():number{
        return this.age;
    }
}
console.log(Person.mark);

示例代码中,声明了一个static字段mark,直接使用Person.mark就可以访问。 

5. instanceof 运算符

instanceof 运算符用于判断对象是否是指定的类型,如果是返回 true,否则返回 false。

class User{
    private name:string;

    constructor(name:string){
        this.name = name;
    }
    public setName(name:string):void{
        this.name = name;
    }
    public getName():string{
        return this.name;
    }
}

class Admin extends User{
    private password:string;

    constructor(name:string,password:string){
        super(name);
        this.password = password;
    }


    public setPassword(password:string){
        this.password = password;
    }
    public getPassword():string{
        return this.password;
    }
}

let user1 = new User("xujingliang");
let admin = new Admin("xujingliang","123456")


console.log(user1 instanceof User);
console.log(user1 instanceof Admin);
console.log(admin instanceof Admin);
console.log(admin instanceof User);

注意:示例代码中,Admin类继承User类,因此用Admin示例化的对象既属于User类有属于Admin类

6. 访问控制修饰符

类成员可访问性定义了类的成员允许在何处被访问。和Java一样TypeScript也为类成员提供了三种可访问性修饰符。

1. public:类的公有成员没有访问限制,可以在当前类的内部、外部以及派生类的内部访问。类的公有成员使用public修饰符标识。在默认情况下,类的所有成员都是公有成员。

2. protected:类的受保护成员允许在当前类的内部和派生类的内部访问,但是不允许在当前类的外部访问。类的受保护成员使用protected修饰符标识。

3. private:类的私有成员只允许在当前类的内部被访问,在当前类的外部以及派生类的内部都不允许访问。类的私有成员使用private修饰符标识。

4. readonly:使用 readonly 关键词设置成员只读,初始化过后,readonly 不管在外部还是内部都不允许再修改。

class User{
    private username:string;
    private password:string;
    private readonly state:boolean = false;

    constructor(username:string,password:string){
        this.username = username;
        this.password = password;
    }

    public setUsername(username:string):void{
        this.username = username;
    }
    public getUsername():string{
        return this.username;
    }

    public setPassword(password:string):void{
        this.password = password;
    }
    public getPassword():string{
        return this.password;
    }

    public setState(state:boolean):void{
        // 此处会报错:Cannot assign to 'state' because it is a read-only property.(2540)
        this.state = state;
    }
}

7. 类的封装

面向对象的三大特性:封装、继承、多态。封装是指将类的某些信息隐藏在类的内部,不允许外部程序直接访问,而是通过该类提供的方法来对隐藏的信息进行操作和访问。我们在程序设计的过程中要追求“高内聚,低耦合”。高内聚就是类的内部数据操作细节自己来完成,不允许外部干涉,低耦合:就是, 仅暴露少量的方法给外部使用

结合访问修饰符private即可实现类的封装

class User{
    private username:string;
    private password:string;

    constructor(username:string,password:string){
        this.username = username;
        this.password = password;
    }

    public setUsername(username:string):void{
        this.username = username;
    }
    public getUsername():string{
        return this.username;
    }

    public setPassword(password:string):void{
        this.password = password;
    }
    public getPassword():string{
        return this.password;
    }
}

上述代码示例中,用户只能通过暴露的setUsername、getUsername、setPassword、getPassword等方法来访问和操作User的username和password属性。

 

8. 类和接口

类与类之间的一些共同点可以用接口去抽象,比如 人 和 动物 都有相同的特点,吃和行走,我们就可以通过接口去约束这两个类的公共能力。接口使用关键字 interface 声明,类可以使用关键字 implements 实现接口。

interface Person{
    name:string
}


class User implements Person{
    name:string;

    constructor(name:string){
        this.name = name;
    }
    public setName(name:string):void{
        this.name = name;
    }
    public getName():string{
        return this.name;
    }
}

class Admin extends User{
    private password:string;

    constructor(name:string,password:string){
        super(name);
        this.password = password;
    }


    public setPassword(password:string){
        this.password = password;
    }
    public getPassword():string{
        return this.password;
    }
}

let user1 = new User("xujingliang");
let admin = new Admin("xujingliang","123456")

9. 抽象类和抽象方法

抽象类在某种程度上跟接口有点类似,它也是用来约束子类当中必须要拥有某些成员,但是不同于接口的是,抽象类可以包含一些具体的实现,而接口只能够是一些成员的抽象,不包含具体的实现,一些比较大的类目建议使用抽象类,比如动物类,因为我们所说的动物它只是一个泛指,并不够具体,那在它的下面一定有更细化的分类,例如小狗小猫之类。

定义抽象类的方式:通过关键词 abstract

1. 类型被定义为抽象类过后,它只能够被继承,不能够再使用 new 的方式去创建对应的实例对象,在这种情况下我们就必须要使用 子类 去继承这个类型。

2. 在抽象类当中我们还可以去定义一些抽象方法,需要注意的是抽象方法也不需要方法体。

3. 当我们的父类有抽象方法时,我们的子类就必须要实现这个方法。

// 抽象类
abstract class Animal{
    // 非抽象方法
    cry(message:string):void{
        console.log(message)
    }

    // 抽象方法 抽象方法不能有具体的实现
    abstract say():void
}

class Dog extends Animal{
    
    // 如果继承的抽象类中有抽象方法,则子类必须去实现抽象方法,否则编译检查报错
    public say():void{
        console.log("I am Dog");
    }

}

 

  • 20
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

进阶的疯狗der

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值