Babel to Class之继承(2)
前言
这是《Babel to Class》系列的第二篇
这个系列主要讲 Class 的普通写法、继承、私有属性和原生构造函数继承 babel
是如何实现这一过程的。
注:文章顺序存在关联,请按照顺序阅读。
前置知识点
super
super
通常用于 Class 继承中调用父类构造函数,以及在方法中调用父类方法。
class Rectangle {
logNbSides() {
return 'I have 4 sides';
}
}
class Square extends Rectangle {
logDescription() {
return super.logNbSides() + ' which are all equal';
}
}
更多的内容请参考 super
Object.create
Object.create(proto,[propertiesObject])
Object.create
用于创建一个新的对象,并将新的对象原型设置在 proto
上,它还支持第二个参数,第二个参数为对象每个对象的值为属性描述符。
var o = Object.create(Object.prototype, {
// foo会成为所创建对象的数据属性
foo: {
writable: true,
configurable: true,
value: 'hello',
},
// bar会成为所创建对象的访问器属性
bar: {
configurable: false,
get: function () {
return 10;
},
set: function (value) {
console.log('Setting `o.bar` to', value);
},
},
});
更多内容请参考 Object.create
Reflect.construct
Reflect.construct(target, argumentsList[, newTarget])
- target:被运行的目标构造函数
- argumentsList:类数组,目标构造函数调用时的参数。
- newTarget:作为新创建对象的原型对象的 constructor 属性, 参考 new.target 操作符,默认值为 target。
Reflect.construct
的作用相当于不用 new
操作符调用构造函数,创建对象。
class Foo {}
var obj = new Foo(...args);
var obj = Reflect.construct(Foo, args);
注意点:
- 使用
Reflect.construct
创建对象,new.target
会自动指向 newTarget, - 如果指定 newTarget,
new.target
会指向 newTarget 且构造函数内部的 this 为 newTarget
更多内容请参考 Reflect.construct
Class
- Class extends 继承的必须是
null
或者function
(class 也是 function),否则抛出错误; - Class 子类 extends 父类的时候必须在
constructor
显式调用super()
,否则抛出错误;
示例代码
class Parent {
constructor() {
this.age = 17;
}
getAge() {
return this.age;
}
static getName() {
return this.name;
}
}
class Child extends Parent {
constructor() {
super();
this.name = 'child';
}
static name = 'child';
getAll() {
return {
name: this.name,
age: this.age,
};
}
}
const child = new Child();
console.log(child.getAll());
console.log(Child.getName());
以下讲解基于上面的内容而来。
babel
'use strict';
function _typeof(obj) {
// ...省略实现
}
function _inherits(subClass, superClass) {
// ...省略实现
}
function _setPrototypeOf(o, p) {
// ...省略实现
}
function _createSuper(Derived) {
// ...省略实现
}
function _possibleConstructorReturn(self, call) {
// ...省略实现
}
function _assertThisInitialized(self) {
// ...省略实现
}
function _isNativeReflectConstruct() {
// ...省略实现
}
function _getPrototypeOf(o) {
// ...省略实现
}
function _defineProperty(obj, key, value) {
// ...省略实现
}
function _classCallCheck(instance, Constructor) {
// ...省略实现
}
function _defineProperties(target, props) {
// ...省略实现
}
function _createClass(Constructor, protoProps, staticProps) {
// ...省略实现
}
var Parent = /*#__PURE__*/ (function () {
function Parent() {
_classCallCheck(this, Parent);
this.age = 17;
}
_createClass(
Parent,
[
{
key: 'getAge',
value: function getAge() {
return this.age;
},
},
],
[
{
key: 'getName',
value: function getName() {
return this.name;
},
},
],
);
return Parent;
})();
var Child = /*#__PURE__*/ (function (_Parent) {
_inherits(Child, _Parent);
var _super = _createSuper(Child);
function Child() {
var _this;
_classCallCheck(this, Child);
_this = _super.call(this);
_this.name = 'child';
return _this;
}
_createClass(Child, [
{
key: 'getAll',
value: function getAll() {
return {
name: this.name,
age: this.age,
};
},
},
]);
return Child;
})(Parent);
_defineProperty(Child, 'name', 'child');
var child = new Child();
console.log(child.getAll());
console.log(Child.getName());
下面函数在 Babel to Class 做了什么(1) 讲解过了
_defineProperty
_classCallCheck
_defineProperties
_createClass
所以不再赘述,重点先看新出现的函数。
_typeof
function _typeof(obj) {
'@babel/helpers - typeof';
return (
(_typeof =
'function' == typeof Symbol && 'symbol' == typeof Symbol.iterator
? function (obj) {
return typeof obj;
}
: function (obj) {
return obj && 'function' == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype
? 'symbol'
: typeof obj;
}),
_typeof(obj)
);
}
这个函数的作用为类型判断。
-
'function' == typeof Symbol && 'symbol' == typeof Symbol.iterator
这段化意思是如果支持原生的Symbol
就直接执行typeof
即可 -
obj && 'function' == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype
这段代码主要是用于判断低版本不兼容Symbol
的浏览器是不是使用了polyfill
库,如果是的化就返回symbol
-
其他情况返回
typeof
结果
_inherits
function _inherits(subClass, superClass) {
if (typeof superClass !== 'function' && superClass !== null) {
throw new TypeError('Super expression must either be null or a function');
}
Object.defineProperty(subClass, 'prototype', {
value: Object.create(superClass && superClass.prototype, {
constructor: { value: subClass, writable: true, configurable: true },
}),
writable: false,
});
if (superClass) _setPrototypeOf(subClass, superClass);
}
- 这段代码首先判断继承的
superClass
是否符合要求 - 其次对
subClass.prototype
进行设置确保prototype
属性非遍历 - 设置原型,将
subClass
的原型设置为superClass
_setPrototypeOf
function _setPrototypeOf(o, p) {
_setPrototypeOf =
Object.setPrototypeOf ||
function _setPrototypeOf(o, p) {
o.__proto__ = p;
return o;
};
return _setPrototypeOf(o, p);
}
设置原型链的方法,首先调用原生的 Object.setPrototypeOf
方法,如果不支持尝试使用 __proto__
,__proto__
是浏览器实现的一个私有属性,在其他环境下不是必须实现。
不过仔细观察,你会发现它还使用了一个函数懒加载,确保判断只执行一次,后续直接不会改变了。
_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);
};
}
这段代码的作用是返回 super
_isNativeReflectConstruct
这个函数我们后面讲解,它的作用就是判断当前环境支不支持Reflect.construct
_possibleConstructorReturn
这个函数后面讲解,作用为判断 Class 符不符合要求
_possibleConstructorReturn
function _possibleConstructorReturn(self, call) {
debugger;
if (call && (_typeof(call) === 'object' || typeof call === 'function')) {
return call;
} else if (call !== void 0) {
throw new TypeError('Derived constructors may only return object or undefined');
}
return _assertThisInitialized(self);
}
这个函数用于判断 Class 和 extends 的 Class 符不符合要求
call && (_typeof(call) === 'object' || typeof call === 'function')
这个相当于判断 isObject,构造函数被调用是可以返回一个对象的
function Foo() {
return { name: 1 };
}
new Foo();
// {name: 1}
如果不是对象也没有默认返回 undefined
,则直接抛出错误
_assertThisInitialized
,这个函数用于判断子类有没有调用super
,如果没有调用抛出错误
_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.construct
,它的判断思路为前置判断,如果不符合早早返回
if (typeof Reflect === 'undefined' || !Reflect.construct) return false;
,不支持原生返回false
if (Reflect.construct.sham) return false;
,polyfill
的则也返回false
if (typeof Proxy === 'function') return true;
,返回true
的原因是因为,Proxy
的兼容性比Reflect
差,如果Proxy
支持Reflect
一定也支持Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {}));
,如果都符合直接运行,前面说过Reflect.construct
支持第三个参数,如果运行没报错返回true
_getPrototypeOf
function _getPrototypeOf(o) {
_getPrototypeOf = Object.setPrototypeOf
? Object.getPrototypeOf
: function _getPrototypeOf(o) {
return o.__proto__ || Object.getPrototypeOf(o);
};
return _getPrototypeOf(o);
}
这段代码为获取原型,跟 _setPrototypeOf
函数类似,如果当前环境支持优先使用 Object.getPrototypeOf
上的,否则回滚。
运行流程
讲解到这里,继承基本讲完了,现在把完整代码贴以下,之后讲一下运行的流程
'use strict';
function _typeof(obj) {
'@babel/helpers - typeof';
return (
(_typeof =
'function' == typeof Symbol && 'symbol' == typeof Symbol.iterator
? function (obj) {
return typeof obj;
}
: function (obj) {
return obj && 'function' == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype
? 'symbol'
: typeof obj;
}),
_typeof(obj)
);
}
function _inherits(subClass, superClass) {
if (typeof superClass !== 'function' && superClass !== null) {
throw new TypeError('Super expression must either be null or a function');
}
Object.defineProperty(subClass, 'prototype', {
value: Object.create(superClass && superClass.prototype, {
constructor: { value: subClass, writable: true, configurable: true },
}),
writable: false,
});
if (superClass) _setPrototypeOf(subClass, superClass);
}
function _setPrototypeOf(o, p) {
_setPrototypeOf =
Object.setPrototypeOf ||
function _setPrototypeOf(o, p) {
o.__proto__ = p;
return o;
};
return _setPrototypeOf(o, p);
}
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);
};
}
function _possibleConstructorReturn(self, call) {
if (call && (_typeof(call) === 'object' || typeof call === 'function')) {
return call;
} else if (call !== void 0) {
throw new TypeError('Derived constructors may only return object or undefined');
}
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;
}
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;
}
}
function _getPrototypeOf(o) {
_getPrototypeOf = Object.setPrototypeOf
? Object.getPrototypeOf
: function _getPrototypeOf(o) {
return o.__proto__ || Object.getPrototypeOf(o);
};
return _getPrototypeOf(o);
}
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;
}
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError('Cannot call a class as a function');
}
}
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 _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
Object.defineProperty(Constructor, 'prototype', { writable: false });
return Constructor;
}
var Parent = /*#__PURE__*/ (function () {
function Parent() {
_classCallCheck(this, Parent);
this.age = 17;
}
_createClass(
Parent,
[
{
key: 'getAge',
value: function getAge() {
return this.age;
},
},
],
[
{
key: 'getName',
value: function getName() {
return this.name;
},
},
],
);
return Parent;
})();
var Child = /*#__PURE__*/ (function (_Parent) {
_inherits(Child, _Parent);
var _super = _createSuper(Child);
function Child() {
var _this;
_classCallCheck(this, Child);
_this = _super.call(this);
_this.name = 'child';
return _this;
}
_createClass(Child, [
{
key: 'getAll',
value: function getAll() {
return {
name: this.name,
age: this.age,
};
},
},
]);
return Child;
})(Parent);
_defineProperty(Child, 'name', 'child');
var child = new Child();
console.log(child.getAll());
console.log(Child.getName());
代码从 var Parent = /*#__PURE__*/ (function () {
这段开始
- 调用
_createClass
,将prototype 属性
和static 属性
设置到 Parent 函数上 - 之后代码往下执行,到
var Child = /*#__PURE__*/ (function (_Parent) {
- 调用
_inherits
,判断 Parent 符合要求,设置Child.prototype
属性,设置 Child 原型 - 调用
_createSuper
,获取super
- 调用
_createClass
,设置prototype 属性
和static 属性
设置到 Child 函数上 - 调用
_defineProperty
,覆盖 Child 上的属性,确保是可以遍历修改的 - new Child
- Child 函数内部执行
_classCallCheck
,确保一定为new
调用 - 在 Child 函数内部执行
_super.call(this)
操作,调用父类构造函数 - Parent 内部,调用
_classCallCheck
,确保new
调用 - 返回 Parent 的 this 值
- Child 内部修改 Parent 返回的 this 值,进行重写
- 执行后续代码...
跟 es5 继承的差异
从上面代码不难看到跟 es5 写的继承有很大差异
prototype
不可以遍历;- 不通过
new
调用显式报错; - Class 的继承不仅是将子类的
prototype
指向父类的prototype
,同时也将子类的原型指向了父类; - 得益于子类原型指向父类,静态方法和属性也可以调用;
最后
如果有什么不正确或者书写错误欢迎指出。