趁着之前刚刚学过es5 的继承,今天来学习一下 es6 的类 以及 类继承
es6 的类其实只是一个语法糖
1 | class Parent_class { |
它与function最大的区别在于,class 只能通过 new 来创建,而function 可以new 也可以直接调用
es6 中 类本身,对应function.prototype,而类的constructor,则对应于function的构造函数(也就是function本身
接下来,我们会从 constructor、实例属性、静态方法、静态属性、getter 和 setter、new 几个方面来对 es6 的类进行解析,用es5的代码来实现对应的功能,并使用babel进行编译,从编译结果中查看babel的转换方式,并最后查看es6 类继承的实现
先来看一些 babel 转义中的工具函数
1 | // instanceof 的polyfill(Symbol?) |
Object.defineProperty定义的属性,其descriptor属性如下
1 | // 数据描述符 |
数据描述符 和 存取描述符 只能二者取其一
需要注意的是,存取描述符中的 get 和 set 都会传入一个 this 对象
实例属性
实例属性可以通过 constructor 和 this.xxx 两种方式来定义
Es6
1 | class Person { |
es5
1 | function Person(name) { |
可以看出,es6中除实例属性和构造函数是挂在函数实例上之外,所有的构造函数都是直接挂到 prototype 上的
babel转义代码
1 | ; |
我们可以看到 Babel 生成了一个 _createClass 辅助函数,该函数传入三个参数,第一个是构造函数,在这个例子中也就是 Person,第二个是要添加到原型上的函数数组,第三个是要添加到构造函数本身的函数数组,也就是所有添加 static 关键字的函数。该函数的作用就是将函数数组中的方法添加到构造函数或者构造函数的原型中,最后返回这个构造函数。
在其中,又生成了一个 defineProperties 辅助函数,使用 Object.defineProperty 方法添加属性。
默认 enumerable 为 false,configurable 为 true,这个在上面也有强调过,是为了防止 Object.keys() 之类的方法遍历到。然后通过判断 value 是否存在,来判断是否是 getter 和 setter。如果存在 value,就为 descriptor 添加 value 和 writable 属性,如果不存在,就直接使用 get 和 set 属性。
static
es6代码
1 | class Person { |
es5
1 | function Person(){} |
Babel 编译代码
1 | var Person = |
Getter 和 setter
Es6
1 | class Person { |
es5
1 | function Person() {} |
babel
1 | var Person = |
继承
es6 extends
1 | class Parent { |
es6 的继承实现方式其实就是 [javascript 之继承 中讲到的寄生组合式继承
super(name) 就是Parent.call(this)
子类必须在 constructor 方法中调用 super 方法,否则新建实例时会报错。这是因为子类没有自己的 this 对象,而是继承父类的 this 对象,然后对其进行加工。如果不调用 super 方法,子类就得不到 this 对象。
也正是因为这个原因,在子类的构造函数中,只有调用 super 之后,才可以使用 this 关键字,否则会报错。
extends 关键字,则是
1
2
3
4
5
6
7
8Child.prototype = Object.create(Parent.prototype, {
constructor: {
configurable: true,
value: Child,
enumerable: false,
writable: true
}
})这里形成了一条
Child.prototype.__proto__ = Parent.prototype
的原型链Es6 中的 static 静态方法也是可以继承的,
Object.setPrototypeOf(Child, Parent)
Child.__proto__ = Parent
Class 作为构造函数的语法糖,同时有 prototype 属性和 proto 属性,因此同时存在两条继承链。
(1)子类的 proto 属性,表示构造函数的继承,总是指向父类。
(2)子类 prototype 属性的 proto 属性,表示方法的继承,总是指向父类的 prototype 属性。
babel 编译代码
1 | ; |
inherit
1 | function _inherits(subClass, superClass) { |
_possibleConstructorReturn
1 | function _possibleConstructorReturn(self, call) { |
也就是说,这个函数会判断 父构造函数的返回值,如果没有返回值的话,那么this就是 self,也就是子类自己的this,而如果父类有返回值并且返回值为object或function的话,this就会指向父类的这个返回值
对于这样一个 class:
1 | class Parent { |
Parent.call(this, name) 的值肯定是 undefined。可是如果我们在 constructor 函数中 return 了呢?比如:
1 | class Parent { |
我们可以返回各种类型的值,甚至是 null:
1 | class Parent { |
我们接着看这个判断:
1 | call && (typeof call === "object" || typeof call === "function") ? call : self; |
注意,这句话的意思并不是判断 call 是否存在,如果存在,就执行 (typeof call === "object" || typeof call === "function") ? call : self
因为 &&
的运算符优先级高于 ? :
,所以这句话的意思应该是:
1 | (call && (typeof call === "object" || typeof call === "function")) ? call : self; |
对于 Parent.call(this) 的值,如果是 object 类型或者是 function 类型,就返回 Parent.call(this),如果是 null 或者基本类型的值或者是 undefined,都会返回 self 也就是子类的 this。
这也是为什么这个函数被命名为 _possibleConstructorReturn
。
总结
和 es5 function 的区别:
只能通过new 调用
类内部所有定义的方法,都是不可枚举的(non-enumerable)
也就是说,
Object.keys(a_class); // []
Object.keys(a_function); // [a,b]
function.prototype = class
class.constructor = function
es6 继承的实现方式:
inherits(Child, Parent)
,实现 原型链的继承关系Object.setPrototypeOf(Child, Parent)
, 实现静态方法的继承关系(也就是es5中的function函数对象上的属性与方法Person.staticProp
)Parent.call(this)
实现实例对象的继承关系,根据 Parent 构造函数的返回值类型确定子类构造函数 this 的初始值 _this。- 最终,根据子类构造函数,修改 _this 的值,然后返回该值。
参考
ES6 系列之 Babel 是如何编译 Class 的(上) #105