本文介绍: 在早期的JavaScript中,我们使用函数模拟类,使用原型实现继承行为。在ES6以后JS也引入类的概念,虽然其本质还是使用原型继承构造函数语法实现的,但是类的写法依旧使得开发人员得心应手;相比JS的类,TS的类可以说是有过之无不及。在TypeScript中,类可以包含属性方法支持继承接口实现,也可以使用修饰符控制访问权限。类的定义使用class关键字,并可以在其中声明构造函数属性方法等。

目录

前言

基本用法

实现接口

继承(extends)

基本用法

访问父类

重写父类(override)

只读关键字(readonly)

存取器(getters/setters)

静态成员(static)

访问修饰符(类成员属性)

JavaScript中的封装

TypeScript中的封装

公共(public)

私有(private)

受保护(protected)

参数的修饰符

抽象类和抽象方法(abstract)

接口(interface),抽象(abstract),重写(override)

结语

参考文章:


前言

本文收录TypeScript知识总结系列文章,欢迎指正! 

在早期的JavaScript中,我们使用函数模拟类,使用原型实现继承行为。在ES6以后JS也引入类的概念,虽然其本质还是使用原型继承构造函数语法实现的,但是类的写法依旧使得开发人员得心应手;相比JS的类,TS的类可以说是有过之无不及。

在TypeScript中,类可以包含属性方法支持继承接口实现,也可以使用修饰符控制访问权限。类的定义使用class关键字,并可以在其中声明构造函数属性方法等。

基本用法

还记得之前的接口吗?我们把要实现的对象或者通过接口的形式抽象表现出来,然后通过实现类或者绑定对象接口具象化

沿用之前的例子展示一下类的基本写法,其中name是类成员(属性),constructor构造函数,getName成员方法

class Animal {
    name: string
    constructor(name: string) {
        this.name = name
    }
    getName() {
        return this.name
    }
}
const animal = new Animal("dog")
console.log(animal.getName());

实现接口

在接口中我们说到了类可以用了实现接口或者对象类型别名,下面是一个例子,我们复习一下

interface IAnimel {
    name: string
    likeMeat: boolean
    getName(): string
}
class Animal implements IAnimel {
    name: string
    likeMeat: boolean
    constructor(name: string, likeMeat: boolean) {
        this.name = name
        this.likeMeat = likeMeat
    }
    getName() {
        return this.name
    }
}
const animal = new Animal("dog", true)
console.log(animal.getName());

继承extends

在TS中类的继承通过extends关键字来实现的。类可以继承其他类的属性和方法,被继承的类称为父类基类,继承的类称为子类派生类;在之前的文章中,我详细的讲述了JS的继承实现及使用,有兴趣可以看看

基本用法

下面是一个继承的基本写法

class Animal {
    name: string
    constructor(name: string) {
        this.name = name
    }
    getName() {
        return this.name
    }
}
class Dog extends Animal {
    constructor(name: string) {
        super(name)
    }
}
console.log(new Dog("阿黄").getName());

可以看到,Dog类继承于Animal类,并拥有Animal的属性及方法

访问父类

在TS中,子类中可以通过super来访问父类的属性和方法。

例如,如果父类一个属性name,可以在子类通过super.name来访问该属性。同时,如果子类中有与父类同名的属性或方法,可以使用super关键字来调用父类的属性或方法,如super.getName()

class Animal {
    name: string
    constructor(name: string) {
        this.name = name;
    }
    getName() {
        return this.name
    }
}

class Dog extends Animal {
    name: string = null
    constructor(name: string) {
        super(name);
        this.name = "小黑"
        console.log(this.name, super.name);// 小黑 undefined
    }
    getAllName(): string {
        return this.name + super.getName()// 获取当前类的name
    }
}
console.log(new Dog("阿黄").getAllName());// 小黑小黑

然而在TS编译成JS时,会出现取不到super.name的情况详细解释可以参考这篇文章

但实际上TS识别出了super关键字取得是父类的name,JS无法实现

如果需要访问super.name,可以使用修改构造函数原型的方式实现效果

Animal.prototype.name = "阿黄"
console.log(new Dog("阿黄").getAllName());// 小黑阿黄

重写父类override

重写父类面向对象常用技术之一,它的定义是在子类中重新定义父类原有方法。重写父类的方法或属性可以修改父类行为,实现多态性;重写可以(也可以不使用,函数名相同即可重写)使用override关键显式标注需要重写的方法,这样可以提高代码可读性和可维护性

下面是一个重写的案例,如果子类没有重写父类的方法,就会报错

class Animal {
    name: string
    constructor(name: string) {
        this.name = name;
    }
    getName() {
        throw new Error("记得重写我")
    }
}
class Dog extends Animal {
    override getName(): string {
        return this.name
    }
}
console.log(new Dog("阿黄").getName());// 阿黄

除此之外,子类可以在重写的方法中调用父类的方法,达到拓展作用

class Animal {
    name: string
    constructor(name: string) {
        this.name = name;
    }
    getName() {
        return this.name
    }
}
class Dog extends Animal {
    override getName(): string {
        const name = super.getName()
        return "名字叫" + name
    }
}
console.log(new Dog("阿黄").getName());// 名字叫阿黄

只读关键字(readonly

在接口中我们提到了readonly只读属性),这种写法在类中同样适用

class Animal {
    readonly name: string
    constructor(name: string) {
        this.name = name;
    }
    setName(name: string) {
        this.name = name// 抛错:无法分配到 "name" ,因为它是只读属性
    }
}

console.log(new Animal("阿黄").setName("小黑"));

上述代码我们可以看到,使用了只读关键字的属性只能在构造数中赋值

tips:只读属性只能在赋值时进行拦截功能set比较像,它无法拦截对象操作

type Arr = string[]
class Animal {
    readonly color: Arr
    constructor(color: Arr) {
        this.color = color;
    }
    setName(name: string) {
        this.color.push(name)
        return this.color
    }
}

console.log(new Animal(["阿黄"]).setName("小黑"));// [ '阿黄', '小黑' ]

存取器(getters/setters)

之前文章中我介绍过JS对象属性的存取,使用Object.defineProperty对象进行读写。在ES6的类中同样引入了这么一个定义属性的方式:类存取器,它是一种特殊类型的函数,用于获取设置类属性的值;它使用get和set关键字定义

class Animal {
    _name: string
    set name(val: string) {
        console.log("设置了值");
        this._name = val
    }
    get name() {
        console.log("获取了值");
        return this._name
    }
}
const animal = new Animal()
animal.name = "阿黄"// 设置了值
console.log(animal.name);// 获取了值 阿黄

静态成员static

类的静态成员也是在JS中就已经实现了的功能,它是指通过类本身直接访问,而不是通过类的实例访问的成员。它们可以是静态属性或静态方法,它的作用是在不需要创建类的实例的情况下访问类的成员,如我们可以通过实现一个单例来举个例子

class Animal {
    name: string
    static __ins: Animal
    constructor(name: string) {
        this.name = name
    }
    static instance() {
        return (name: string) => {
            !!!this.__ins && (this.__ins = new Animal(name))
            return this.__ins
        }
    }
}
const animal1 = Animal.instance()("阿黄").name
const animal2 = Animal.instance()("小黑").name
console.log(animal1, animal2, Animal.__ins);// 阿黄 阿黄 Animal

上面代码中,我定义了一个static属性 __ins 以及一个static方法 instance ,在使用时直接使用类名.属性获取,此时如果使用下面的代码无法获取静态属性

new Animal("阿黄").__ins

访问修饰符(类成员属性)

封装面向对象编程技术之一,在代码中可以通过控制属性和方法的可访问性,达到封装的目的

JavaScript中的封装

在早期的JS中我们通常使用闭包写法来实现函数私有变量的使用及储存,并且习惯使用 _变量名的形式对私有变量进行标记

var Animal = (function () {
  var _name = null;
  function Animal(name) {
    _name = name;
  }
  Animal.prototype = {
    getName() {
      return _name;
    },
  };
  return Animal;
})();

到了ES11(2020年)针对JS类的内部私有化的功能也被实现了,使用 #变量名 的方式将属性标记私有变量

class Animal {
    #name;
    getName() {
        return this.#name;
    }
    setName(name) {
        this.#name = name;
    }
}
const animal = new Animal();
animal.setName("阿黄");
console.log(animal.getName());

TypeScript中的封装

TS提供三个访问修饰符publicprivateprotected

公共public

类中的属性和方法默认使用的就是公共public修饰符表示成员可以在类的内部和外部被访问

class Animal {
    public name: string;
    constructor(name: string) {
        this.name = name
    }
    public getName() {
        return this.name;
    }
}
const animal = new Animal("阿黄")
console.log(animal.getName(), animal.name);// 阿黄 阿黄

私有private)

私有private)用于限制成员只能在类内部访问,不能在类外部访问(子类也无法访问属性及方法)

class Animal {
    private name: string;
    constructor(name: string) {
        this.name = name
    }
    private getName() {
        return this.name;// 正常访问
    }
}
const animal = new Animal("阿黄")
console.log(animal.getName(), animal.name);// 抛错:属性为私有属性,只能在类中访问

结合之前说的存取器,我们可以实现一个完整的TS类存取器

class Animal {
    private _name: string;
    get name() {
        return this._name;
    }
    set name(val: string) {
        this._name = val
    }
}

保护protected

如果给属性或者方法定义为受保护protected)的,说明该属性或方法只能在类内部或派生类(子类)中访问

class Animal {
    protected _name: string;
    constructor(name: string) { this._name = name }
}
class Dog extends Animal {
    getName = () => {
        return this._name
    }
}
const dog = new Dog("阿黄")
console.log(dog.getName());// 阿黄
console.log(dog._name);// 抛错:属性受保护,只能在类及其子类中访问

参数修饰

结合上述修饰符,我们还可以在类的构造函数的参数直接赋予修饰符达到简写的目的,例如

class Animal {
    constructor(readonly name: string, private likeMeat?: boolean, public age?: number, protected color?: string) { }
}
const animal = new Animal("阿黄", true, 10, "black")
console.log(animal); // Animal { name: '阿黄', likeMeat: true, age: 10, color: 'black' }

上述代码的效果和以下代码效果相同

class Animal {
    readonly name: string
    private likeMeat?: boolean
    public age?: number
    protected color?: string
    constructor(name: string, likeMeat?: boolean, age?: number, color?: string) {
        this.name = name
        this.likeMeat = likeMeat
        this.age = age
        this.color = color
    }
}
const animal = new Animal("阿黄", true, 10, "black")
console.log(animal); // Animal { name: '阿黄', likeMeat: true, age: 10, color: 'black' }

抽象类抽象方法(abstract)

同一个抽象类不同子类可以实现相同抽象方法,但具体的实现方式不同,这是多态的实现。

抽象类抽象方法是多态的基础。在TS中抽象类是一种特殊的类,不能被直接实例化,只能被子类继承,抽象类中的抽象方法必须在子类中被实现,通过强制子类实现指定的属性或方法,可以保证子类在一定程度上的一致性

我们来看一个例子

abstract class Animal {
    constructor(protected name: string) { }
    abstract getName(): string
}
class Dog extends Animal {
    getName(): string {
        return this.name
    }
}
const animal = new Dog("阿黄")
console.log(animal.getName())

例子中我使用定义了一个抽象类Animal,并且定义了getName函数,此时的类无法直接实例化,因为getName没有被实现,需要使用其他类(Dog类)继承基类并实现这个函数,最后实例化子类,如果子类也是抽象类,那么则继续被继承,直到抽象类中的抽象函数全部被实现为止

接口(interface),抽象(abstract),重写(override

概念上来说这三者都是实现多态方式,它们三者的差异体现在约束方式约束的程度上

重写主要是针对方法的实现进行约束:遵循父类方法的参数返回值类型

接口主要是针对行为进行约束:类需要实现哪些方法以及这些方法应该具有什么样的参数返回

抽象则同时约束行为的实现并提供部分方法实现:子类可以直接继承并使用父类提供的方法或属性,并且被父类约束方法的签名返回值类型

结语

篇文章详细介绍了TypeScript中类的定义及使用,使用类实现接口,类的继承、封装、多态,

只读属性,存取器,静态属性并且结合接口总结了多态的表现方式

最后,感谢你的阅读,有任何问题欢迎评论私信,如果文章对你有帮助,还希望支持一下博主

参考文章

类(class) – TypeScript 中文手册

类 · TypeScript 入门教程

原文地址:https://blog.csdn.net/time_____/article/details/129426326

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_47430.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注