Почему instanceof не работает над экземплярами подклассов Error под babel- node?

Я вижу, что оператор instanceof не работает в экземплярах подклассов Error, когда работает под babel-node версия 6.1.18/ Node версия 5.1.0 на OS X. Почему это? Тот же код хорошо работает в браузере, попробуйте 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"]
}

Скомпилированный вывод

Это JavaScript, скомпилированный babel 6.1.18:

'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));

Ответ 1

tl; dr Если вы находитесь на Babel 6, вы можете использовать https://www.npmjs.com/package/babel-plugin-transform-builtin-extend

Расширение встроенных типов, таких как Array и Error, и таких никогда не поддерживалось в 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 классы были изменены, чтобы быть более spec-compliant в том, как обрабатывается подклассы, а побочным эффектом является то, что теперь вышеуказанный код все равно не будет работать, но он не будет работать иначе, чем до. Это описано в 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 определенно красноречиво и может не работать в 100% случаев.