细数TypeScript中的类与ES6中的类的异同

759 阅读7分钟

一、类的由来

在比较TypeScript中的类与ES6中的类之前,我们先了解一下类。我们知道,在ES6之前,生成实例对象的传统方式是通过构造函数,使用ES5的特性来模拟类似于类的行为,但是各种策略都有自己的问题,也有相应的妥协,所以实现继承的代码也显得非常冗长和混乱,因此,在ES6中引入了类的概念,作为对象的模板。可以使用class关键字类定义类,class其实就是一个语法糖,他实现的绝大多数功能在ES5都能做到,只是class的写法让对象原型的更加清晰简洁,更像是面向对象编程。

类可以包含构造函数方法,实例方法,获取函数,设置函数和静态方法,但是这些都不是必需的,空的类定义照样有效,一个类大概就长下面这样:

class Animal {
 constructor(name) {
     this.name = name
 }
 foo(){}
 }

上面,我们声明了一个Animal类,它包含constructor构造函数和一个name属性,同时含有一个foo方法,这就是类大概的样式了;

二、Typescript中的类与ES6的类的区别

1、类的实现

首先,我们声明一个TS的类,如下:

class Dog {
    constructor(name: string) {
        this.name = name
    }
    name: string
    run() {}
}

由上可以看出,TS的类相比于ES的类,其实例属性name和构造函数参数name都多了一个string的类型注解,同时,TS中的类的属性必须要有一个初始的值或者在构造函数中进行初始化,上面的例子,如果我们将this.name = name注释掉,就会得到一个报错:

class Dog {
    constructor(name: string) {
        // this.name = name
    }
    name: string // 属性“name”没有初始化表达式,且未在构造函数中明确赋值。
    run() {}
}

上面的例子会报错,提示name属性没有初始值,且在构造函数中没有进行初始化,解决的方法有三个,第一个是像最开始声明的时候那样在构造函数中进行初始化,也可以直接给name属性赋一个初始值,name: string = 'wangCai,或者你可以将name属性变成可选属性name?: string,这三种方法都可以解决报错的问题,总之在TS中类的属性必须要有初始值;

2、类的继承

上面,我们介绍了类的基本实现上的不同,接下来我们来看一下在类的继承中他两有什么不同,下面我们声明一个Dog类的派生类,和ES一样,使用extends实现类的继承;

class Husky extends Dog {
    constructor(name: string, color: string) {
        super(name)
        this.color = color
    }
    color: string
}

上面我们代码中我们实现了一个Dog的派生类Husky,并用super关键字引用了Dog的原型,并声明了自己的属性color,值得注意的是,派生类的继承必须调用super,并且只能在super调用之后使用this,这是TS的强制规定,同时super中应传入父类需要的所有参数。

3、类的成员修饰符

我们知道,在ES6中并没有类的成员修饰符,这也是TS对ES类的一种扩展。类的成员修饰符主要有public、private、protected,readyonly等几种,这里主要介绍前面三种;

公共属性public

在TS中。成员都默认是public,也可以明确的将一个成员标记为public,即允许我在类的内外调用这个成员,如下:

class Dog1 {
    public constructor(name: string) {
        this.name = name
    }
    public name: string
    public run() {}
}

const dog = new Dog1('Wang cai')
console.log(dog.name) //Wang cai
dog.run() //Wang cai

可以看到,我们可以在类的内外部访问公共属性和方法;

私有属性private

接下来我们了解private,如果一个成员被标记为private,它就不能被外部访问,只能在声明他的类的内部访问,举个例子:

class Dog {
    constructor(name: string) {
        this.name = name
    }
    private name: string
    run() {
        console.log(this.name)
    }
}

const dog = new Dog("wangCai")
console.log(dog.name) // 属性“name”为私有属性,只能在类“Dog”中访问

上面的例子中,我们将name属性标记为private,并创建了dog实例,并通过run方法和实例打印name属性时,发现,run方法可以正常访问name属性,但是通过dog.name访问name属性时就会报错;同样,私有成员也不能被子类调用。

class Husky extends Dog1 {
  constructor() {
    super('xiaohei')
  }
  foo(){
    console.log(this.name); //属性“name”为私有属性,只能在类“Dog1”中访问
  }
}

我们也可以给构造函数添加private标记,意思就是这个类既不能被实例化,也不能被继承,当我们想实现一个单例模式的时候,就可以用到这个;

受保护成员protected

受保护成员和私有成员很相似,唯一不同的就是,受保护成员可以在子类中被访问,但是不能被实例成员访问。

class Dog {
    constructor(name: string) {
        this.name = name
    }
    name: string
    protected run() {
        console.log(this.name)
    }
}

const dog = new Dog("wangCai")
dog.run() // 属性“run”受保护,只能在类“Dog”及其子类中访问

class Husky extends Dog {
    constructor(name: string, color: string) {
        super(name)
        this.color = color
        this.run()
    }
    color: string
}

上面例子中我们同时在实例中和子类中访问父类的受保护成员run方法,结果在实例中报错,但是在子类中是正常的;同样,也可以给构造函数添加protected标记,表示类只可以被继承但不能被实例化;

可以给构造函数参数添加标记,这样的话就可以将参数直接变成实例的属性了,如给name参数添加public标记之后,就不能再重复name属性了:

class Dog {
    constructor(public name: string) {
        this.name = name
    }
    protected run() {
        console.log(this.name)
    }
}

4、抽象类与多态

在TS和ES中都有抽象类的概念,他们都是供其他类继承但是本身不能被实例化,但是TS没有沿用ES6的抽象类的概念,TS中使用abstract定义抽象类和抽象方法;这也是TS对ES6类的一种扩展。

abstract class Person {
    abstract shopping(): void
}

抽象类的抽象方法不包含具体的实现但是必须在派生类中实现,抽象类中的抽象方法和接口类似,两者都是只包含方法签名但是不包含方法体,同时抽象方法可以包含访问修饰符;

abstract class Person {
    constructor(public name: string) {
        this.name = name
    }
    abstract cry(): void
    speak(): void {
        console.log("I can speak")
    }
}

class Man extends Person {
    constructor() {
        super("Jean")
    }
    cry(): void {
        console.log("I can crying")
    }
}

class Woman extends Person {
    constructor() {
        super("lina")
    }
    cry():void {
        console.log("One cry, two make, three hang")
    }
    shopping(): void {
        console.log("I like shopping")
    }
}

上面的例子中声明了一个抽象类和抽象方法,然后在子类中进行了具体的实现;抽象类和抽象方法的好处就是可以抽离出一些共性,有利于代码的复用和扩展,另外,也可以实现多态,就像上面的代码,在父类中声明了一个cry方法,然后在两个子类中有不同的实现,然后在运行的时候根据不同的对象进行不同的操作,这样就实现了运行时的绑定,接着上面的代码,我们实现多态:

const man = new Man()
const woman = new Woman()
const person: Person[] = [man, woman]

person.forEach(i => {
    i.cry()
})

上面的代码会先后打印出"I can crying""One cry, two make, three hang",其实这就实现了多态。

三、总结

可以看出,TS的类对ES6的类做了更多的扩展,继承了ES6的类的一些特性的同时给类的属性加了修饰符等,这样我们在使用类的时候就能更加的灵活方便,也使它更像一门面向对象的编程语言了。所以在使用TS的时候,可以不断的锻炼我们面向对象的思维;

以上就是我总结的一些TS的类和ES6类的区别了,因为我也是初学TS,所以内容比较基础,希望对你有帮助,如果还有其他不一样的地方,希望你能指出让我也学习一下,谢谢!