【babel】babel如何编译class与extends
示例
Foo
类拥有这些属性:
- 两个成员变量
age
、name
。 - 一个成员方法
getInfo()
。 - 一个静态变量
skin
。 - 一个静态方法
getSkin()
。
class Foo {
constructor() {
this.name = "zkb";
}
age = 22;
static skin = "black";
getInfo() {
return this.name + this.age;
}
static getSkin() {
return Foo.skin;
}
}
babel 编译结果
对于 Foo
类,babel 的编译结果如下(删除了类型转换相关代码):
"use strict";
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, _toPropertyKey(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;
}
function _defineProperty(obj, key, value) {
key = _toPropertyKey(key);
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true,
});
} else {
obj[key] = value;
}
return obj;
}
var Foo = /*#__PURE__*/ (function () {
function Foo() {
_classCallCheck(this, Foo);
_defineProperty(this, "age", 22);
this.name = "zkb";
}
_createClass(
Foo,
[
{
key: "getInfo",
value: function getInfo() {
return this.name + this.age;
},
},
],
[
{
key: "getSkin",
value: function getSkin() {
return Foo.skin;
},
},
]
);
return Foo;
})();
_defineProperty(Foo, "skin", "black");
babel 对 class 的实现
先看编译后的最后一段代码:
var Foo = /*#__PURE__*/ (function () {
function Foo() {
_classCallCheck(this, Foo);
_defineProperty(this, "age", 22);
this.name = "zkb";
}
_createClass(
Foo,
[
{
key: "getInfo",
value: function getInfo() {
return this.name + this.age;
},
},
],
[
{
key: "getSkin",
value: function getSkin() {
return Foo.skin;
},
},
]
);
return Foo;
})();
_defineProperty(Foo, "skin", "black");
可以看出最终的 Foo
是一个函数,这个函数内部调用了 _classCallCheck(this, Foo)
和 _defineProperty(this, "age", 22)
两个方法,并且包含了 constructor()
函数的内容。
_classCallCheck(this, Foo)
的逻辑为:如果调用者(this
)不是 Foo
的实例,则抛出异常,这主要是为了阻止非 Foo
实例来调用 Foo
方法。
Foo.call({}); // 抛出异常:Cannot call a class as a function
const foo = new Foo();
Foo.call(foo); // 正常执行
绑定成员变量
_defineProperty(this, "age", 22)
为 this
绑定属性,逻辑为:如果 this
上有 arg
则用 Object.defineProperty()
设置其值,否则直接赋值。
这样处理的目的是:如果有 Foo
的实例将 age
属性设置为不可写,那么在调用 Foo.call(foo)
时就会将 age
属性重新设置为可写(但是如果手动将 age
属性设置为不可配置则会在这一步抛出异常)。
function _defineProperty(obj, key, value) {
key = _toPropertyKey(key); // 将 key 转换为原始类型
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true,
});
} else {
obj[key] = value;
}
return obj;
}
const foo = new Foo();
// 设置 foo.age 为无法写入
Object.defineProperty(foo, "age", {
value: 999,
writable: false,
});
// 此时 foo.age 已无法修改
foo.age = 888;
console.log(foo.age); // 999
// 重新初始化后可修改
Foo.call(foo);
foo.age = 888;
console.log(foo.age); // 888
// 设置 foo.age 为无法配置
Object.defineProperty(foo, "age", {
value: 999,
configurable: false,
});
Foo.call(foo); // 抛出异常:Uncaught TypeError: Cannot redefine property: age
绑定静态变量
在初始化完毕 Foo
后调用 _defineProperty(Foo, "skin", "black")
来为 Foo
绑定静态变量,其实就是给方法设置一个属性值。
var Foo = /*#__PURE__*/ (function () {
// ...
})();
_defineProperty(Foo, "skin", "black");
绑定成员方法和静态方法
在内部 Foo
方法中调用 _createClass()
来给 Foo
绑定成员方法和静态方法:
_createClass(
Foo,
[
{
key: "getInfo",
value: function getInfo() {
return this.name + this.age;
},
},
],
[
{
key: "getSkin",
value: function getSkin() {
return Foo.skin;
},
},
]
);
_createClass()
方法的定义如下:
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
Object.defineProperty(Constructor, "prototype", { writable: false });
return Constructor;
}
逻辑很简单:成员方法绑定到 Foo.prototype
上,静态方法绑定到 Foo
上。
babel 对 extends 的实现
在示例中添加一个类 Dto
来继承 Foo
:
class Dto extends Foo {
constructor() {
super();
this.name = "zkb";
}
}
编译结果中会在最后加入如下代码:
var Dto = /*#__PURE__*/ (function (_Foo) {
_inherits(Dto, _Foo);
var _super = _createSuper(Dto);
function Dto() {
var _this;
_classCallCheck(this, Dto);
_this = _super.call(this);
_this.name = "zkb";
return _this;
}
return _createClass(Dto);
})(Foo);
可以看出 Dto
的内部初始化流程和 Foo
类似,只是增加了 _inherits(Dto, _Foo)
和 _super.call(this)
的调用。
继承父类
不难看出 _inherits(Dto, _Foo)
方法完成了继承的功能,_inherits()
方法定义如下:
// 设置对象原型
function _setPrototypeOf(o, p) {
_setPrototypeOf = Object.setPrototypeOf
? Object.setPrototypeOf.bind()
: function _setPrototypeOf(o, p) {
o.__proto__ = p;
return o;
};
return _setPrototypeOf(o, p);
}
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 },
});
Object.defineProperty(subClass, "prototype", { writable: false });
if (superClass) _setPrototypeOf(subClass, superClass);
}
在 _inherits()
中抛去异常判断逻辑之外干了这几件事:
- 将
Dto.prototype
指向Object.create(superClass.prototype)
, 继承Foo
的原型方法。 - 将
Dto.prototype.constructor
指向Dto
。 - 将
Dto.__proto__
指向Foo
,继承Foo
的静态方法。
super()
编译后的代码用 _createSuper()
方法创建了 _super
,定义如下:
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);
};
}
这里的 if
语句只是区分了调用 Super 的方式,因此不需要关注 Reflect
相关的内容,只关注 Super.apply()
即可,两种方式是等价的。
_possibleConstructorReturn(this, result)
是检测方法,如果有问题则抛出异常,否则返回 this
。
关键代码:
var Super = _getPrototypeOf(Derived)
。在
_inherits()
方法中已经将Dto
的原型指向为Foo
,所以这里定义的Super
其实就是Foo
函数本身。result = Super.apply(this, arguments)
。相当于
Foo.apply(this, arguments)
。
总结
class
降级编译之后用原型模式来实现类:
function Foo() {
// 函数体相当于 constructor
this.name = "zkb"; // this 上绑定的相当于成员属性
}
// 成员变量、成员方法绑定在实例的 __proto__ 也就是 Foo.prototype 上
Foo.prototype.age = 22;
Foo.prototype = function getInfo() {
return this.name + this.age;
};
// 静态变量、静态方法绑在 Foo 上
Foo.skin = "black";
Foo.getSkin = function () {
return Foo.skin;
};
extends
降级编译之后是原型继承的模式:
function Foo() {}
function Dto() {
Dto.__proto__.apply(this, arguments);
}
Dto.prototype = Object.create(Foo.prototype);
Dto.prototype.constructor = Dto;
Dto.__proto__ = Foo;
Q&A
为什么要做 A.prototype.constructor=A 这样的修正?
constructor 其实没有什么用处,只是 JavaScript 语言设计的历史遗留物。
由于 constructor 属性是可以变更的,所以未必真的指向对象的构造函数,只是一个提示。
不过,从编程习惯上,我们应该尽量让对象的 constructor 指向其构造函数,以维持这个惯例。