为什么在babel-node下,instanceof不能用于Error子类的实例?

81
我发现在babel-node版本6.1.18/Node版本5.1.0上运行OS X时,instanceof运算符不适用于Error子类的实例。为什么会这样?相同的代码在浏览器中运行良好,请尝试我的fiddle进行示例。

以下代码在浏览器中输出true,但在babel-node下是false:

class Sub extends Error {
}

let s = new Sub()
console.log(`The variable 's' is an instance of Sub: ${s instanceof Sub}`)

我能想象这只能归咎于babel-node中的一个错误,因为instanceof对于除Error之外的其他基类有效。

.babelrc

{
  "presets": ["es2015"]
}

编译输出

这是由Babel 6.1.18编译的JavaScript代码:

'use strict';

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var Sub = (function (_Error) {
  _inherits(Sub, _Error);

  function Sub() {
    _classCallCheck(this, Sub);

    return _possibleConstructorReturn(this, Object.getPrototypeOf(Sub).apply(this, arguments));
  }

  return Sub;
})(Error);

var s = new Sub();
console.log('The variable \'s\' is an instance of Sub: ' + (s instanceof Sub));

我无法重现你的问题。我已经使用在线的 Babel REPL 和 babel-node(最新版本)进行了测试。 - Denys Séguret
你在使用 Babel 时使用了哪个预设?也许发布转译后的脚本可以帮助重现问题。 - CodingIntrigue
@DenysSéguret 谢谢,我会进一步检查。 - aknuds1
@RGraham 这个小程序在浏览器中使用Babel?关键是,显然Babel在浏览器和Node中的工作方式不同。 - aknuds1
1
@RGraham,我已经发布了编译后的脚本。 - aknuds1
2个回答

91

简而言之,如果你使用Babel 6,你可以使用https://www.npmjs.com/package/babel-plugin-transform-builtin-extend来扩展像ArrayError等内置类型。在Babel中,从未支持过这样的扩展。在真正的ES6环境中,这是完全有效的,但是要使其与旧版浏览器兼容非常困难。在Babel 5中,“工作”并没有抛出错误,但是从扩展子类实例化的对象无法按照预期工作。

class MyError extends Error {}

var e1 = new MyError();
var e2 = new Error();

console.log('e1', 'stack' in e1);
console.log('e2', 'stack' in e2);

导致

e1 false
e2 true

虽然它没有出错,但子类没有像错误一样正确地获得“堆栈”。同样,如果您扩展 Array,它可能会表现得有些像数组,并具有数组方法,但它并不完全像数组。

Babel 5 文档特别将此作为需要注意的类的边缘情况。

在 Babel 6 中,类被更改为更符合规范的处理子类的方式,其副作用是现在上述代码仍然无法正常工作,但与以前不同的是,它将以不同的方式无法工作。这已经在https://phabricator.babeljs.io/T3083中讨论过,但我将在此详细说明一个潜在的解决方案。

要返回 Babel 5 的子类行为(请记住,这仍然不正确或不推荐),您可以在自己的临时类中包装内置构造函数,例如:

function ExtendableBuiltin(cls){
    function ExtendableBuiltin(){
        cls.apply(this, arguments);
    }
    ExtendableBuiltin.prototype = Object.create(cls.prototype);
    Object.setPrototypeOf(ExtendableBuiltin, cls);

    return ExtendableBuiltin;
}

有了这个帮助程序,您不必再手动完成

class MyError extends Error {}

class MyError extends ExtendableBuiltin(Error) {}

然而就您的情况而言,您说您正在使用Node 5.x。Node 5支持原生ES6类而无需转译。我建议您使用它们,放弃es2015预设,改用node5,以获得原生类等功能。

class MyError extends Error {}

如果您不使用Node 4/5或最近的Chrome版本,您可能需要考虑使用类似https://www.npmjs.com/package/error的东西。您还可以尝试https://www.npmjs.com/package/babel-plugin-transform-builtin-extend中的approximate选项,它和Babel 5的行为相同。请注意,非approximate行为肯定是边缘情况,并且可能无法在所有情况下正常工作。


1
谢谢。那么创建新的异常类的推荐方法是什么? - aknuds1
2
毕竟,MDN 推荐 使用原型继承 Error 来创建新的异常类。据我所知,这应该对应于 ES6 中的子类化,除非我忽略了什么。我还发现了这篇文章,介绍了 ES6 类语义学,其中有一个关于子类化 Error 的例子。 - aknuds1
1
我稍微扩展了我的回答。在ES6中进行子类化比你想象的要复杂得多,并且不能很好地映射到ES5中。 - loganfsmyth
1
它已经修复了吗?还是说它永远都不会被修复?他们在phabricator上给这个问题打了一个"Wontfix"标签,这有点令人失望,因为我认为如果Babel按照规范正确地完成了工作,我应该期望这个功能能够正常工作。 - Michał Miszczyszyn
4
对于这些用例,我只是在使用 ES5 编写它们,例如 var CustomError = function(message){ Error.call(this, message) this.message = message } - Ally
显示剩余8条评论

8
如果编译目标设置为"es5",则对子类化的错误使用instanceof会失败。我在tsconfig.json中将目标设置为"es6",这样instanceof就能产生正确的结果了。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接