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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Person{
constructor(name, age){
this.name = name
this.age = age
}

static type = 'being'

sayName (){
return this.name
}

static intro(){
console.log("")
}
}

class Men extends Person{
constructor(name, age){
super(name, age)
this.gender = 'male'
}
}

转化后的函数太长了,我就不完整贴在这里了,想看完整版的可以去上面的连接自己看,我们现在开始一步步分析这个转化后的函数。

辅助函数

_inherits

1
2
3
4
5
6
7
8
9
function _inherits(subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function");
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: { value: subClass, writable: true, configurable: true }
});
if (superClass) _setPrototypeOf(subClass, superClass);
}

看这个函数的名字是用来做继承用的,也就是extends的时候会调用这个函数。

这个函数首先上来是判断父类是否符合条件,如果不符合,直接抛出异常。

紧接着是调用Object.create方法,这个方法的作用是:

**Object.create()**方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__

也就是说:

1
2
3
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: { value: subClass, writable: true, configurable: true }
});

这段代码就完成了基于原型链的继承,这段代码的结果就是,通过superClass.prototype创建了一个新的对象,新对象的__proto__指向superClass.prototype,然后将这个对象赋值给subClass.prototype。

也就是说,subClass.prototype.__proto__ = superClass.prototype

这样一来,subClass的原型链上就有了superclass的原型。

然后就是最后一步的_setPrototypeOf,我们继续看看这个函数是做什么的

_setPrototypeOf

1
2
3
4
5
6
7
8
9
function _setPrototypeOf(o, p) {
_setPrototypeOf =
Object.setPrototypeOf ||
function _setPrototypeOf(o, p) {
o.__proto__ = p;
return o;
};
return _setPrototypeOf(o, p);
}

这个函数的作用也是构造原型链,其核心目的就是这句

1
o.__proto__ = p

结合上一个辅助函数

1
_setPrototypeOf(subClass, superClass)

那结果就是subClass.__proto__ = superClass

_isNativeReflectConstruct

1
2
3
4
5
6
7
8
9
10
11
12
13
function _isNativeReflectConstruct() {
if (typeof Reflect === "undefined" || !Reflect.construct) return false;
if (Reflect.construct.sham) return false;
if (typeof Proxy === "function") return true;
try {
Boolean.prototype.valueOf.call(
Reflect.construct(Boolean, [], function () {})
);
return true;
} catch (e) {
return false;
}
}

要看懂这段代码,就要明白Reflect是什么,Reflect.construct是什么

首先Reflect其实也是ES6的新语法,具体可以做什么可以去看MDN,不是我们目前的重点,我们暂时只关注其中一点:

Reflect.construct(target, argumentsList[, newTarget\])

对构造函数进行 new 操作,相当于执行 new target(...args)

那这个函数的作用就比较明显了,就是看看当前的执行环境下能不能用Reflect.construct去创建新的对象。

_getPrototypeOf

1
2
3
4
5
6
7
8
function _getPrototypeOf(o) {
_getPrototypeOf = Object.setPrototypeOf
? Object.getPrototypeOf
: function _getPrototypeOf(o) {
return o.__proto__ || Object.getPrototypeOf(o);
};
return _getPrototypeOf(o);
}

这个函数就是获取传入参数的原型链上的下一个原型,也可以理解为它直接继承的对象。

_typeof

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function _typeof(obj) {
"@babel/helpers - typeof";
if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
_typeof = function _typeof(obj) {
return typeof obj;
};
} else {
_typeof = function _typeof(obj) {
return obj &&
typeof Symbol === "function" &&
obj.constructor === Symbol &&
obj !== Symbol.prototype
? "symbol"
: typeof obj;
};
}
return _typeof(obj);
}

这段代码首先通过if-else创建了函数变量_typeof,由于是es5代码,所以没有const和let,而这种直接变量赋值的方式其实与var相同,所以会存在变量提升

结果就是在函数作用域中声明了一个函数,赋值给_typeof,然后调用这个函数返回结果。

置于为什么会有这个判断,我应该又要在这里留一个坑了,我们暂时不管它,就当它是为了浏览器的兼容性好了。

_possibleConstructorReturn

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function _possibleConstructorReturn(self, call) {
if (call && (_typeof(call) === "object" || typeof call === "function")) {
return call;
}
return _assertThisInitialized(self);
}

function _assertThisInitialized(self) {
if (self === void 0) {
throw new ReferenceError(
"this hasn't been initialised - super() hasn't been called"
);
}
return self;
}

首先我们先要知道void 0 === undefined 是 true.

再回来看这段代码,就是说如果call是对象或者函数,直接返回call。

如果不是,那就看看self是不是undefined,是的话就抛出异常,不是则返回self

_createSuper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function _createSuper(Derived) {
var hasNativeReflectConstruct = _isNativeReflectConstruct();
return function _createSuperInternal() {
var Super = _getPrototypeOf(Derived),
result;
if (hasNativeReflectConstruct) {
var NewTarget = _getPrototypeOf(this).constructor;
result = Reflect.construct(Super, arguments, NewTarget);
} else {
result = Super.apply(this, arguments);
}
return _possibleConstructorReturn(this, result);
};
}

好了,看了上面那么多辅助函数,最终都是在这里使用的。

我们好好梳理一下这个函数是做什么的:

  • 首先判断当前执行环境能不能调用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
2
3
4
5
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}

这个函数很简单,就是用来检测不能像普通函数那样调用class,比如class Persion你不能直接Persion()

_defineProperties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}

function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}

这两个函数一起的作用其实就是在target上不断定义新的属性

_createClass

1
2
3
4
5
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}

结合上面的_defineProperties函数,这个函数三个参数,第一个参数是构造函数,第二个参数是所有普通属性的数组,第三个是所有静态属性的数组,所以这个函数的作用其实就是:

  • 将class中的普通属性定义在构造函数的原型上,这样当我们new一个实例的时候,就可以在实例的原型链上找到这些普通的属性

    看过我前面关于原型链博客的应该对我的公式有印象:

    a = new A() => a.__proto__ = A.prototype

  • 将class中的静态属性定义在构造函数上,这样我们就可以直接在构造函数上点出静态方法。

动态的生成代码

好了,有了上面的这些辅助函数,我就可以看看我们一开始定义的class是怎么利用这些辅助函数来实现的了

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
var Person = /*#__PURE__*/ (function () {
function Person(name, age) {
_classCallCheck(this, Person);

this.name = name;
this.age = age;
}

_createClass(
Person,
[
{
key: "sayName",
value: function sayName() {
return this.name;
}
}
],
[
{
key: "intro",
value: function intro() {
console.log("");
}
}
]
);

return Person;
})();

_defineProperty(Person, "type", "being");

var Men = /*#__PURE__*/ (function (_Person) {
_inherits(Men, _Person);

var _super = _createSuper(Men);

function Men(name, age) {
var _this;

_classCallCheck(this, Men);

_this = _super.call(this, name, age);
_this.gender = "male";
return _this;
}

return Men;
})(Person);
  • 首先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.prototype

    subClass.__proto__ = superClass

    放在这里,也就是说

    Men.prototype.__proto__ = _Person.prototype

    Men.__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本质返回的还是一个函数。