ECMAScript 6 中的类

ECMAScript 5 中的近类结构

ECMAScript 5 及早期版本中没有类的概念,最相近的思路是创建一个自定义类型:首先创建一个构造函数,然后定义另一个方法并赋值给构造函数的原型。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function PersonType(name) {
this.name = name;
}
PersonType.prototype.sayName = function () {
console.log(this.name);
}
var person = new PersonType("Adele");
person.sayName(); // Adele
console.log(person instanceof PersonType); // true
console.log(person instanceof Object); // true

许多模拟类的 JS 库都是基于这个模式进行开发,而且 ECMAScript 6 中的类也借鉴了类似的方法。

类声明

ECMAScript 6 有一种与其他语言中类似的类特性:类声明,同时,它也是 ECMAScript 6 中最简单的类形式。

基本的类声明语法

要声明一个类,首先编写 class 关键字,紧跟着的是类的名字,其他部分的语法类似于对象字面量方法的简写形式,但不需要在类的各元素之间使用逗号分隔。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class PersonClass {
// 等价于 PersonType 构造函数
constructor(name) {
this.name = name;
}
// 等价于 PersonType.prototype.sayName
sayName() {
console.log(this.name);
}
}
let person = new PersonClass("Adele");
person.sayName(); // Adele
console.log(person instanceof PersonClass); // true
console.log(person instanceof Object); // true

类表达式

基本的类表达式语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let PersonClass = class {
// 等价于 PersonType 构造函数
constructor(name) {
this.name = name;
}
// 等价于 PersonType.prototype.sayName
sayName() {
console.log(this.name);
}
}
let person = new PersonClass("Adele");
person.sayName(); // Adele
console.log(PersonClass.name); // PersonClass
console.log(person instanceof PersonClass); // true
console.log(person instanceof Object); // true

类声明和类表达式功能相似,二者最重要的区别是 name 属性不同,匿名类表达式的 name 属性值是一个空字符串,而类声明的 name 属性值为类名。

命名类表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let PersonClass = class PersonClass2{
// 等价于 PersonType 构造函数
constructor(name) {
this.name = name;
}
// 等价于 PersonType.prototype.sayName
sayName() {
console.log(this.name);
}
}
let person = new PersonClass("Adele");
person.sayName(); // Adele
console.log(PersonClass.name); // PersonClass
console.log(typeof PersonClass); // true
console.log(typeof PersonClass2); // undefined

静态成员

ECMAScript 5 及早期版本中,直接将方法添加到构造函数中来模拟静态成员是一种常见的模式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function PersonType(name) {
this.name = name;
}
// 静态方法
PersonType.create = function(name) {
return new PersonType(name);
}
// 实例方法
PersonType.prototype.sayName = function () {
console.log(this.name);
}
var person = PersonType.create("Adele");
person.sayName();

ECMAScript 6 的类语法简化了创建静态成员的过程,在方法或访问器属性名前使用正式的静态注释即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class PersonClass{
// 等价于 PersonType 构造函数
constructor(name) {
this.name = name;
}
// 等价于 PersonType.prototype.sayName
sayName() {
console.log(this.name);
}
// 等价于 PersonType.create
static create(name) {
return new PersonClass(name);
}
}
let person = PersonClass.create("Adele");
person.sayName(); // Adele

不可在实例中访问静态成员,必须要直接在类中访问静态成员

继承与派生类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function Rectangle(length, width) {
this.length = length;
this.width = width;
}
Rectangle.prototype.getArea = function () {
return this.length * this.width;
}
function Square(length) {
Rectangle.call(this, length, length);
}
Square.prototype = Object.create(Rectangle.prototype, {
constructor: {
value: Square,
enumerable: true,
writable: true,
configurable: true
}
})
var square = new Square(3);
console.log(square.getArea()); // 9
console.log(square instanceof Square); // true
console.log(square instanceof Rectangle); // true

类的出现让我们可以更轻松地实现继承功能,使用熟悉的 extends 关键字可以指定类继承的函数。原型会自动调整,通过调用 super() 方法即可访问基类的构造函数。以下为上段示例的等价版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Rectangle {
constructor(length, width) {
this.length = length;
this.width = width;
}
getArea() {
return this.length * this.width;
}
}
class Square extends Rectangle {
constructor(length) {
// 等价于 Rectangle.call(this, length, length);
super(length, length);
}
}
var square = new Square(3);
console.log(square.getArea()); // 9
console.log(square instanceof Square); // true
console.log(square instanceof Rectangle); // true

继承自其他类的类被称作派生类,如果在派生类中指定了构造函数则必须要调用 super(),如果不这样程序就会报错。如果选择不使用构造函数,则当创建新的类实例时会自动调用 super() 并传入所有参数。

类方法遮蔽

派生类中的方法总会覆盖基类中的同名方法。

1
2
3
4
5
6
7
8
9
10
class Square extends Rectangle {
constructor(length) {
super(length, length);
}
// 覆盖并遮蔽 Rectangle.prototype.getArea() 方法
getArea() {
return this.length * this.length;
}
}

如果想调用基类中的该方法,则可以调用 super.getArea() 方法,就像这样:

1
2
3
4
5
6
7
8
9
class Square extends Rectangle {
constructor(length) {
super(length, length);
}
getArea() {
return super.getArea();
}
}