本文从 babel 编译后的代码分析理解 class 的特性和继承。
前置知识
Object.setPrototypeOf(obj, prototype) / Object.getPrototypeOf(obj)
设定 obj 的原型对象为 prototype / 获取 obj 的原型
Object.create(proto [, propertiesObject])
创建原型对象为 proto 的对象
// 简易版没加限制
Object.create = function(proto){
function F(){}
F.prototype = proto;
return new F();
}
Object.defineProperty
对象上添加新属性
Reflect.construct(target, args [, newTarget])
相当于 new target(...args),返回的是 target 构造函数,原型指向 newTarget。
注意:函数直接执行 new.target 为 undefined,new 调用有值,Reflect 会自动指向。
例如
function OneClass() {
console.log('OneClass');
console.log(new.target);
}
OneClass.prototype.sayOne = function () { console.log('sayOne') }
function OtherClass() {
console.log('OtherClass');
console.log(new.target);
}
OtherClass.prototype.sayOther = function () { console.log('sayOther') }
var obj2 = Reflect.construct(OneClass, [], OtherClass);
// OneClass function OtherClass { ... }
// 等效
var obj3 = Object.create(OtherClass.prototype);
OneClass.apply(obj3, []);
// OneClas undefined
class 编译后是啥
class Parent {
p = 'parent';
hiP = () => {}
constructor(){
this.age = 10;
}
sayParent(){}
static hi(){}
}
转换后
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);
return Constructor;
}
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;
}
var Parent = /*#__PURE__*/ (function () {
"use strict";
function Parent() {
_classCallCheck(this, Parent);
_defineProperty(this, "p", "parent");
_defineProperty(this, "hiP", function () {});
this.age = 10;
}
_createClass(
Parent,
[
{
key: "sayParent",
value: function sayParent() {}
}
],
[
{
key: "hi",
value: function hi() {}
}
]
);
return Parent;
})();
执行流程:
可以看出 class 编译成一个立即函数,执行时,初始化一个 Parent 函数,调用 _createClass,分别往构造函数(static xx)和原型上添加方法(定义在class 内的方法),最后返回 Parent 函数。
当 new Parent() 时,执行 Parent 函数,内部会往实例上添加属性。
ES6 class 特性
-
必须使用 new 调用;
-
不存在变量提升;
-
默认即是严格模式;
-
内部所有定义的方法都是不可枚举的;
-
子类继承父类,默认构造函数中会调用父类,显示声明 constructor 时,要有 super();
class 经 babel 编译后就是函数表达式
class Parent {}
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
var Parent = function Parent() {
"use strict";
_classCallCheck(this, Parent);
};
很容易看出:默认是严格模式 、必须 new 调用 ,当直接函数调用 Parent(),会报错。
class 不能提升 的目的是保证继承的有效性,由于 class 编译后就是函数表达式,如下代码会出错,
new Parent();
class Parent {}
new Parent();
var Parent = ...;
先执行 new ,发现 Parent 未定义,报错;
class Parent {}
class Child extends Parent {
}
var Parent = ...;
var Child = ...;
现在来看,假设 class 会提升,当执行到 Child 时,Child 到 Parent 前面,找不到 Parent 报错。
内部所有定义的方法都是不可枚举的
class Parent {
sayParent(){}
static hi(){}
}
Object.keys(Parent) // []
Object.keys(Parent.prototype) // []
// bable 编译后的关键代码也能看出
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);
}
}
继承 extends
// 源
class Parent {
p = 'parent';
hiP = () => {}
constructor(){
this.age = 10;
}
sayParent(){}
static hi(){}
}
class Child extends Parent{
}
// 转换
var Child = /*#__PURE__*/ (function (_Parent) {
"use strict";
_inherits(Child, _Parent);
var _super = _createSuper(Child);
function Child() {
_classCallCheck(this, Child);
return _super.apply(this, arguments);
}
return Child;
})(Parent);
// 继承的关键代码
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);
}
Child 初始化时,通过原型式继承,Child.prototype 原型对象为 Parent.prototype,Child 的原型对象为 Parent,这样,Child 能访问到 Parent 上的静态方法,new Child() 的实例能访问 Parent.prototype 对象上的方法属性。
_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);
};
}
new Child ,执行该方法,Derived 为 Child ,所以 Super 为 Parent,返回 Parent 实例
以上是子类没有声明 constructor 时,会默认调父类
function Child() {
_classCallCheck(this, Child);
// 调父类
return _super.apply(this, arguments);
}
当子类有 constuctor 时,会怎么样呢?
- 没写
super(),会报错,提示this hasn't been initialised - super() hasn't been called
// 源
class Child extends Parent{
constructor(){}
}
// 转换
var Child = /*#__PURE__*/ (function (_Parent) {
"use strict";
_inherits(Child, _Parent);
var _super = _createSuper(Child);
function Child() {
var _this;
_classCallCheck(this, Child);
return _possibleConstructorReturn(_this);
}
return Child;
})(Parent);
// call 为 undefined 走 _assertThisInitialized
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);
}
// self 为 undefined
function _assertThisInitialized(self) {
if (self === void 0) {
throw new ReferenceError(
"this hasn't been initialised - super() hasn't been called"
);
}
return self;
}
- 有 super() 时,编译如下,和没有声明 constructor 类似
// 源
class Child extends Parent{
constructor(){
super();
}
}
// 转换
var Child = /*#__PURE__*/ (function (_Parent) {
"use strict";
_inherits(Child, _Parent);
var _super = _createSuper(Child);
function Child() {
_classCallCheck(this, Child);
return _super.call(this);
}
return Child;
})(Parent);
- this 在 super() 前
class Child extends Parent{
constructor(){
this.child = 'dxx';
super();
}
}
var Child = /*#__PURE__*/ (function (_Parent) {
"use strict";
_inherits(Child, _Parent);
var _super = _createSuper(Child);
function Child() {
var _this;
_classCallCheck(this, Child);
_this.child = "dxx";
return (_this = _super.call(this));
}
return Child;
})(Parent);
new Child() _this 为 undefined,到 _this.child = "dxx" 报错
- this 在 super() 后
class Child extends Parent{
constructor(){
super();
this.child = 'dxx';
}
}
var Child = /*#__PURE__*/ (function (_Parent) {
// ...
function Child() {
var _this;
_classCallCheck(this, Child);
_this = _super.call(this);
_this.child = "dxx";
return _this;
}
return Child;
})(Parent);
证明 Child 实例 _this ,由父类产生。
总结
我们从 babel 编译后的代码看到 class 是语法糖,实际就是函数表达式。当子类 extends 父类,帮我们实现了继承,注意构造函数中使用 this,要在 super() 调用后。