ES6 引入了 class 关键字,可以更方便地定义类和继承关系,但是在使用 Babel 编译 ES6 代码时,可能会出现多个 class 之间的依赖关系不正确的问题。本文将介绍这个问题的产生原因和解决方法,并提供示例代码。
问题描述
假设有两个类 A 和 B,其中 B 继承自 A,代码如下:
// www.javascriptcn.com code example
class A {
constructor() {
console.log('A constructor');
}
}
class B extends A {
constructor() {
super();
console.log('B constructor');
}
}
export { A, B };在使用 Babel 编译后,得到的代码如下:
// www.javascriptcn.com code example
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
class A {
constructor() {
console.log('A constructor');
}
}
class B extends A {
constructor() {
super();
console.log('B constructor');
}
}
exports.A = A;
exports.B = B;可以看到,编译后的代码将 A 和 B 分别导出了,但是在浏览器中使用时,可能会出现错误,提示 A 没有定义。这是因为 B 继承自 A,但是 A 还没有被定义,导致 B 的构造函数无法执行。
问题原因
这个问题的原因是,Babel 默认会按照代码的顺序将 class 转换为 ES5 的函数形式,并将它们分别导出。在上面的示例代码中,Babel 会先将 B 转换为函数,但是这个函数中使用了 A,而 A 还没有被转换为函数,导致出现错误。
解决方法
解决这个问题的方法是,将所有的 class 都转换为函数后再导出。可以使用 @babel/plugin-transform-runtime 插件来实现这个功能。这个插件会将所有的 class 转换为函数,并将它们放到一个运行时环境中,避免了多个 class 之间的依赖关系不正确的问题。
安装 @babel/plugin-transform-runtime:
npm install --save-dev @babel/plugin-transform-runtime
在 .babelrc 文件中添加插件配置:
// www.javascriptcn.com code example
{
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": 3
}
]
]
}其中,corejs 选项指定使用的 core-js 版本。@babel/runtime 和 core-js 都是必须安装的依赖。
修改示例代码:
// www.javascriptcn.com code example
import { inheritsLoose as _inheritsLoose, wrapNativeSuper as _wrapNativeSuper } from "@babel/runtime/helpers/esm/inheritsLoose";
import _classCallCheck from "@babel/runtime/helpers/esm/classCallCheck";
import _createClass from "@babel/runtime/helpers/esm/createClass";
var A = /*#__PURE__*/function () {
function A() {
_classCallCheck(this, A);
console.log('A constructor');
}
_createClass(A, null, [{
key: "test",
value: function test() {
console.log('A test');
}
}]);
return A;
}();
var B = /*#__PURE__*/function (_A) {
_inheritsLoose(B, _A);
function B() {
var _this;
_this = _A.call(this) || this;
console.log('B constructor');
return _this;
}
_createClass(B, null, [{
key: "test",
value: function test() {
console.log('B test');
}
}]);
return B;
}( /*#__PURE__*/_wrapNativeSuper(A));
export { A, B };可以看到,所有的 class 都被转换为了函数,然后导出了 A 和 B。
示例代码
完整的示例代码如下:
// www.javascriptcn.com code example
// index.js
import { A, B } from './classes';
A.test();
B.test();
// classes.js
class A {
constructor() {
console.log('A constructor');
}
static test() {
console.log('A test');
}
}
class B extends A {
constructor() {
super();
console.log('B constructor');
}
static test() {
console.log('B test');
}
}
export { A, B };使用 Babel 编译后的代码为:
// www.javascriptcn.com code example
// index.js
"use strict";
var _classes = require("./classes");
_classes.A.test();
_classes.B.test(); // classes.js
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.B = exports.A = void 0;
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/esm/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/esm/createClass"));
var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/esm/possibleConstructorReturn"));
var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/esm/getPrototypeOf"));
var _inheritsLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/esm/inheritsLoose"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var A = /*#__PURE__*/function () {
function A() {
console.log('A constructor');
}
(0, _createClass2.default)(A, null, [{
key: "test",
value: function test() {
console.log('A test');
}
}]);
return A;
}();
exports.A = A;
var B = /*#__PURE__*/function (_A) {
(0, _inheritsLoose2.default)(B, _A);
function B() {
var _this;
_this = _A.call(this) || this;
console.log('B constructor');
return _this;
}
(0, _createClass2.default)(B, null, [{
key: "test",
value: function test() {
console.log('B test');
}
}]);
return B;
}( /*#__PURE__*/(0, _wrapNativeSuper2.default)(A));
exports.B = B;
function _wrapNativeSuper(Class) {
var _cache = typeof Map === "function" ? new Map() : undefined;
_wrapNativeSuper = function _wrapNativeSuper(Class) {
if (Class === null || !_isNativeFunction(Class)) return Class;
if (typeof Class !== "function") {
throw new TypeError("Super expression must either be null or a function");
}
if (typeof _cache !== "undefined") {
if (_cache.has(Class)) return _cache.get(Class);
_cache.set(Class, Wrapper);
}
function Wrapper() {
return _construct(Class, arguments, _getPrototypeOf(Wrapper));
}
Wrapper.prototype = Object.create(Class.prototype, {
constructor: {
value: Wrapper,
enumerable: false,
writable: true,
configurable: true
}
});
return Object.setPrototypeOf(Wrapper, Class);
};
return _wrapNativeSuper(Class);
}
function _construct(Parent, args, Class) {
if (Class === null) Class = Function;
var ctor = Object.getOwnPropertyDescriptor(Parent, 'constructor').value;
if (Class === Function) {
return ctor.apply(args);
}
ctor = ctor.bind(Function.prototype.apply.call(Class, null, args));
return Object.create(Class.prototype, {
constructor: {
value: ctor,
enumerable: false,
writable: true,
configurable: true
}
});
}
function _isNativeFunction(fn) {
return Function.toString.call(fn).indexOf("[native code]") !== -1;
}可以看到,所有的 class 都被转换为了函数,并且 A 和 B 之间的依赖关系得到了正确处理。
来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/67d172e6a941bf7134314809