ES6 Class 源码分析
上一篇关于React的博客中留了一个关于Es6 class的坑,关于class与function之间的关系,我们这篇博客来讨论一下。
我们带着两个问题来看这篇博客:
- class是如何用function实现的
- new class和new function有什么区别
class如何用function实现
原型链
我们都知道,class其实是function的一个语法糖,是基于原型链的的,所以想要看懂这篇博客,建议大家先去看看我的前面一篇讲原型链的博客:用公式讲清楚原型链
利用babel将class转化为es5代码
我们可以写一段class的代码,然后利用babel在线工具将其转化为es5的代码,然后一步步分析。
首先我们先写一段,class的代码
1 | class Person{ |
转化后的函数太长了,我就不完整贴在这里了,想看完整版的可以去上面的连接自己看,我们现在开始一步步分析这个转化后的函数。
辅助函数
_inherits
1 | function _inherits(subClass, superClass) { |
看这个函数的名字是用来做继承用的,也就是extends的时候会调用这个函数。
这个函数首先上来是判断父类是否符合条件,如果不符合,直接抛出异常。
紧接着是调用Object.create方法,这个方法的作用是:
**Object.create()
**方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__
。
也就是说:
1 | subClass.prototype = Object.create(superClass && superClass.prototype, { |
这段代码就完成了基于原型链的继承,这段代码的结果就是,通过superClass.prototype创建了一个新的对象,新对象的__proto__
指向superClass.prototype,然后将这个对象赋值给subClass.prototype。
也就是说,subClass.prototype.__proto__
= superClass.prototype
这样一来,subClass的原型链上就有了superclass的原型。
然后就是最后一步的_setPrototypeOf
,我们继续看看这个函数是做什么的
_setPrototypeOf
1 | function _setPrototypeOf(o, p) { |
这个函数的作用也是构造原型链,其核心目的就是这句
1 | o.__proto__ = p |
结合上一个辅助函数
1 | _setPrototypeOf(subClass, superClass) |
那结果就是subClass.__proto__
= superClass
_isNativeReflectConstruct
1 | function _isNativeReflectConstruct() { |
要看懂这段代码,就要明白Reflect是什么,Reflect.construct是什么
首先Reflect其实也是ES6的新语法,具体可以做什么可以去看MDN,不是我们目前的重点,我们暂时只关注其中一点:
Reflect.construct(target, argumentsList[, newTarget\])
对构造函数进行
new
操作,相当于执行new target(...args)
。
那这个函数的作用就比较明显了,就是看看当前的执行环境下能不能用Reflect.construct去创建新的对象。
_getPrototypeOf
1 | function _getPrototypeOf(o) { |
这个函数就是获取传入参数的原型链上的下一个原型,也可以理解为它直接继承的对象。
_typeof
1 | function _typeof(obj) { |
这段代码首先通过if-else创建了函数变量_typeof
,由于是es5代码,所以没有const和let,而这种直接变量赋值的方式其实与var相同,所以会存在变量提升
结果就是在函数作用域中声明了一个函数,赋值给_typeof
,然后调用这个函数返回结果。
置于为什么会有这个判断,我应该又要在这里留一个坑了,我们暂时不管它,就当它是为了浏览器的兼容性好了。
_possibleConstructorReturn
1 | function _possibleConstructorReturn(self, call) { |
首先我们先要知道void 0 === undefined
是 true.
再回来看这段代码,就是说如果call是对象或者函数,直接返回call。
如果不是,那就看看self是不是undefined,是的话就抛出异常,不是则返回self
_createSuper
1 | function _createSuper(Derived) { |
好了,看了上面那么多辅助函数,最终都是在这里使用的。
我们好好梳理一下这个函数是做什么的:
- 首先判断当前执行环境能不能调用Reflect.construct,如果可以hasNativeReflectConstruct就是true。
- 然后这个函数执行的结果,其实返回了另外一个函数_createSuperInternal,这个函数中用到了外层的参数,所以这是个闭包。
- 那我们看一下,这个闭包中做了什么:
- 获取Derived的原型作为Super
- 如果当前环境可以调用Reflect.construct(hasNativeReflectConstruct是true)
- var NewTarget = _getPrototypeOf(this).constructor;获取当前函数执行者的原型链上的上一级的构造函数,并赋值给NewTarget。
- result = Reflect.construct(Super, arguments, NewTarget);调用 new Super,参数是arguments,创建的对象的构造函数是NewTarget
- 如果hasNativeReflectConstruct是false,直接在this上调用apply,这种继承方式其实就是借用构造函数继承这种继承方式,还有许多其他的继承方式,有兴趣可以去看我的另外一篇博客:https://kingworker.cn/javascript-继承/
- 最终调用_possibleConstructorReturn(this, result),结果就是,如果result如果是对象或者函数,则返回result,否则如果this不是undefined,则返回this,都不符合则抛出错误。
_classCallCheck
1 | function _classCallCheck(instance, Constructor) { |
这个函数很简单,就是用来检测不能像普通函数那样调用class,比如class Persion你不能直接Persion()
_defineProperties
1 | function _defineProperties(target, props) { |
这两个函数一起的作用其实就是在target上不断定义新的属性
_createClass
1 | function _createClass(Constructor, protoProps, staticProps) { |
结合上面的_defineProperties
函数,这个函数三个参数,第一个参数是构造函数,第二个参数是所有普通属性的数组,第三个是所有静态属性的数组,所以这个函数的作用其实就是:
将class中的普通属性定义在构造函数的原型上,这样当我们new一个实例的时候,就可以在实例的原型链上找到这些普通的属性
看过我前面关于原型链博客的应该对我的公式有印象:
a = new A() => a.
__proto__
= A.prototype将class中的静态属性定义在构造函数上,这样我们就可以直接在构造函数上点出静态方法。
动态的生成代码
好了,有了上面的这些辅助函数,我就可以看看我们一开始定义的class是怎么利用这些辅助函数来实现的了
1 | var Person = /*#__PURE__*/ (function () { |
首先Persion定义为一个立即执行函数的返回结果,这个返回结果还是一个函数,所以我们可以得知,最后Persion其实还是一个函数,我们仍然称这个函数为Constructor Persion。
这个Constructor Persion函数做了什么?
- 首先,调用
_classCallCheck(this, Person);
,这个是我们刚才分析的辅助函数,保证我们的这个Constructor Persion函数不是直接调用的,而是放在new后面当做构造函数。 - 然后在实例上添加两个属性,name和age
- 首先,调用
调用_createClass,我们刚才分析过了, 这个函数三个参数分别是构造函数,普通属性的数组,静态属性的数组,所以这一步之后,Persion.prototype上有了一个新的属性,叫做sayName,Persion上有了一个新的静态属性,叫做intro。
最后返回Constructor Persion
因为type是Class Persion的静态属性,所以,在Constructor Persion上定义type,这样就可以直接在构造函数上找到。
Men同样也定义为一个立即执行函数的返回结果,这个返回结果也是一个函数。我们称这个函数为Constructor Men。
首先调用
_inherits(Men, _Person)
,通过刚才我们分析的_inherits
作用可知,其作用就是subClass.prototype.
__proto__
= superClass.prototypesubClass.
__proto__
= superClass放在这里,也就是说
Men.prototype.
__proto__
= _Person.prototypeMen.
__proto__
= _Person这样就完成了原型链的继承
这里要注意的一点是,这个Men是下面声明的function Men,而不是外层的Men,因为函数声明会提升
var _super = _createSuper(Men)
我们刚才分析过这个
_createSuper
函数,它会返回另外一个函数,这个函数的作用是获取Men.__proto__
作为Super,然后以函数调用者为上下文去调用Super,而上面的_inherits(Men, _Person)
已经使得Men.__proto__
= _Person。所以这一步的结果是返回了一个函数,会在以调用者为上下文去调用Persion函数
其次定义Constructor Men(虽然代码顺序不是这样的,但是函数声明会变量提升),那这个Constructor Men做了什么呢?
- 同样检查下是不是直接调用的Men。
- 调用_super,也就是Men的一个实例上调用Persion,这就是典型的借用构造函数继承。
- 在Men实例上挂载gender属性。
返回Constructor Men
我们最后来分析下结果:
- 首先我们得到了两个函数Persion和Men,这两个函数都只能通过new去调用,否则会抛出异常。
- 其次这两个构造函数本身上都定义了声明的静态属性,构造函数的原型上定义了普通属性。
- Men.prototype.
__proto__
= _Person.prototype - Men.
__proto__
= _Person - Men的构造函数中会通过在Men实例上调用Persion构造函数的方法去进行继承。
new function和new class
看到这里并且看明白的小伙伴应该明白了,并没有什么区别,只是多了一些内置的检查,帮你做了原型链的继承而已。
class本质返回的还是一个函数。