上一篇关于React的博客中留了一个关于Es6 class的坑,关于class与function之间的关系,我们这篇博客来讨论一下。
我们带着两个问题来看这篇博客:
- class是如何用function实现的
- new class和new function有什么区别
class如何用function实现
原型链
我们都知道,class其实是function的一个语法糖,是基于原型链的的,所以想要看懂这篇博客,建议大家先去看看我的前面一篇讲原型链的博客:用公式讲清楚原型链
利用babel将class转化为es5代码
我们可以写一段class的代码,然后利用babel在线工具将其转化为es5的代码,然后一步步分析。
首先我们先写一段,class的代码
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
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__
。
也就是说:
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
function _setPrototypeOf(o, p) {
_setPrototypeOf =
Object.setPrototypeOf ||
function _setPrototypeOf(o, p) {
o.__proto__ = p;
return o;
};
return _setPrototypeOf(o, p);
}
这个函数的作用也是构造原型链,其核心目的就是这句
o.__proto__ = p
结合上一个辅助函数
_setPrototypeOf(subClass, superClass)
那结果就是subClass.__proto__
= superClass
_isNativeReflectConstruct
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
function _getPrototypeOf(o) {
_getPrototypeOf = Object.setPrototypeOf
? Object.getPrototypeOf
: function _getPrototypeOf(o) {
return o.__proto__ || Object.getPrototypeOf(o);
};
return _getPrototypeOf(o);
}
这个函数就是获取传入参数的原型链上的下一个原型,也可以理解为它直接继承的对象。
_typeof
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
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
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,这种继承方式其实就是借用构造函数继承这种继承方式,还有许多其他的继承方式,有兴趣可以去看我的另外一篇博客:kingworker.cn/javascript-…
- 最终调用_possibleConstructorReturn(this, result),结果就是,如果result如果是对象或者函数,则返回result,否则如果this不是undefined,则返回this,都不符合则抛出错误。
_classCallCheck
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
这个函数很简单,就是用来检测不能像普通函数那样调用class,比如class Persion你不能直接Persion()
_defineProperties
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
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是怎么利用这些辅助函数来实现的了
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.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本质返回的还是一个函数。