本来打算介绍下 ts 中的类,但是转念一想还是先来说说 js 中的类吧。
定义类的方法
在 ES6 中,我们可以使用 class
关键字直接定义类。定义的方式类似于函数的定义方式,可以分为 2 种:
- 类声明(比较常见):
javascript
// 例 1
class Animal {}
- 类的表达式:
javascript
const Animal = class {}
小提示:如果通过 typeof 来查看例 1 中声明的类 Animal 的类型,console.log(typeof Animal)
结果会是 "function",这是因为 typeof
可能的返回值是固定的那么几个,里面没有 class。
类的特点
其实,class
定义类从本质上可以看成是 ES5 中的构造函数的语法糖,例 1 可以看成是定义了一个构造函数: function Animal() {}
,所以构造函数的那些特性类也有:
- 类也有 prototype 属性;
- 类的原型对象的 [[Prototype]] 属性默认也是指向
Object.prototype
:
javascript
console.log(Animal.prototype.__proto__ === Object.prototype) // true
- 类的原型对象也有 constructor 属性,默认指向类本身:
javascript
console.log(Animal.prototype.constructor === Animal) // true
new
一个类的时候,同样会让生成的对象(最后返回作为实例对象)的隐式原型指向类的显示原型:
javascript
const animal = new Animal()
console.log(animal.__proto__ === Animal.prototype) // true
不难发现,如果之前关于构造函数与原型、原型链那部分知识掌握好了,类的学习就会轻松许多。
类的构造方法(constructor)
在构造函数中,我们可以给函数传参,在函数中通过 this.xx = yy
的形式最终让生成的实例拥有值为 yy 的 xx 属性。在类中,我们则可以通过定义构造函数的方式来实现。注意,构造函数只能定义 1 个,且函数名必须为 constructor
:
javascript
// 例 2.1
class Animal {
constructor(height, weight) {
this.height = height
this.weight = weight
}
}
const animal = new Animal(2, 200)
console.log(animal) // Animal {height: 2, weight: 200}
构造函数 constructor
会在我们通过 new
创建类的实例时调用:const animal = new Animal(height, weight)
,即使我们像例 1 那样不自己手写定义,创建实例时还是会执行默认的constructor
函数。执行构造函数时做的事情和 new
一个构造函数背后做的事情如出一辙:
- 在内存中创建一个新的空对象;
- 类的
prototype
的值会被赋给第 1 步创建的对象的 [[Prototype]] 属性; - 构造函数内部的
this
,也会指向第 1 步创建的对象; - 执行函数的内部代码(函数体);
- 如果构造函数没有返回非空对象,则返回第 1 步创建的对象(其实也就是 this)。
类的方法
类里面可以定义的方法分为 3 种:
1. 实例方法
实例属性的定义我们通过 constructor
函数解决了,那么实例的方法我们在类中是如何定义的呢?很简单,直接在类中定义即可,比如我们想给 Animal 类添加一个实例方法 eat
:
javascript
// 例 2.2
class Animal {
// ...
eat() {
console.log('我是 Animal 类的实例的方法')
}
}
如此,eat 方法就会被添加到 Animal.prototype
上,可以打印 Animal 原型对象自身的所有属性来验证:
javascript
console.log(Reflect.ownKeys(Animal.prototype)) // [ 'constructor', 'eat' ]
方法还有另外一种比较少见的写法如下:
javascript
// 例 2.2.1
class Animal {
// ...
eat = () => {
console.log('我是 Animal 类的实例的方法')
}
}
注意第 4 行这里不是用 :
而是用 =
赋值的形式。
2. 访问器方法(存储器属性)
在类里面还可以通过 getter 和 setter 方法定义实例的属性,以便之后在获取或修改该属性时进行一些拦截操作:
javascript
// 例 3
class Animal {
constructor() {
// ...
this._flag = '我是个属性'
}
// ...
get flag() {
return this._flag
}
set flag(val) {
this._flag = val
}
}
如上,我们就给 Animal 的实例添加了属性 flag
:
javascript
const animal = new Animal()
console.log(animal.flag) // 我是个属性
注意:
- 虽然
flag
是个访问器方法,但其实是个属性(存储器属性),获取的时候animal.flag
是不能加括号的。 - 如果没有设置 set 方法,那么在 js 中,执行
animal.flag = 'xxx'
并不会报错(ts 会报错),但也不会生效。
3. 静态方法
我们可以通过 static
关键字定义静态方法。
javascript
// 例 4.1
class Animal {
// ...
static getFlag() {
console.log('我是 Animal 类的静态方法')
}
}
不同于实例方法,静态方法直接通过类调用:Animal.getFlag()
,而无需生成实例对象。比如 Date.now()
的 now()
方法就是 Date 类的一个静态方法
静态属性
虽然 ES6 是不支持静态属性的,但我们可以通过 static 配合访问器方法来定义类的静态属性。
javascript
// 例 4.2
class Animal {
// ...
static get flag() {
return '我是 Animal 类的静态属性'
}
}
这样我们就可以通过 Animal.flag
获取到 Animal 类的静态属性 flag 了。注意和例 3 进行对比,例 4.2 中我们不能在第 5 行返回 this._flag
,因为当我们通过 Animal.flag
获取 flag 时,this
指向的是 Animal,Animal 本身是没有 _flag
属性的。