Чому instanceof не працює на екземплярах підкласів Error під babel-node?


77

Я бачу, що instanceofоператор не працює на екземплярах Errorпідкласів, коли працює під версією babel-node версії 6.1.18 / Node версії 5.1.0 в OS X. Чому це? Той самий код добре працює у браузері, спробуйте мою скрипку для прикладу.

Наступний код виводиться trueв браузері, тоді як під babel-node він помилковий:

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

Я не можу відтворити вашу проблему. Я тестував як за допомогою мережевої репліки babel, так і за допомогою babel-node (остання версія).
Денис Сегурет

Який пресет ви використовуєте з Babel? Можливо, розміщення перекладеного сценарію допомогло б відтворити проблему
CodingIntrigue

@ DenysSéguret Дякую, збираюся перевірити далі.
aknuds1

@RGraham Скрипка використовує Babel у браузері? Справа в тому, що Babel по-різному працює в браузері проти Node, очевидно.
aknuds1

1
@RGraham Я опублікував скомпільований сценарій.
aknuds1

Відповіді:


88

tl; dr Якщо ви використовуєте Babel 6, ви можете використовувати https://www.npmjs.com/package/babel-plugin-transform-builtin-extend

Розширення вбудованих типів типу Arrayі Errorта ніколи в Бабелі не підтримувалось. Він цілком дійсний у реальному середовищі 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. Вузол 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Варіант того ж поведінку від Вавилонської 5. Остерігайтеся , що не- approximateповедінку безумовно краю Casey і може не працювати в 100% випадків.


1
Ага спасибі. То який тоді рекомендований спосіб створення нових класів винятків ??
aknuds1

2
Зрештою, MDN рекомендує прототипове успадкування Errorдля створення нових класів винятків. AFAICT, це повинно відповідати підкласу в ES6, якщо я щось не помічаю. Я також знайшов цю статтю про семантику класу ES6, яка має приклад саме підкласифікації Error.
aknuds1

1
Я трохи розширив свою відповідь. Підкласифікація в ES6 набагато складніша, ніж можна було б очікувати, і вона не найкраще відображається в ES5.
loganfsmyth

1
Це вже виправлено? Або це колись буде виправлено? Вони розмістили Wontfixтегу на цій проблемі у фабрикаторі, це наче розчаровує, тому що я думав, що слід очікувати, що ця функція запрацює, принаймні, якщо Babel зробив це правильно і відповідно до специфікації.
Міхал Міщишин

4
Для цих випадків використання я щойно писав їх у ES5, наприкладvar CustomError = function(message){ Error.call(this, message) this.message = message }
Еллі

1

instanceofне працюватиме для підкласифікованих помилок, якщо для цілі компіляції встановлено значення "es5". Я встановив ціль "es6" у своєму tsconfig.jsonі instanceofдав правильний результат.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.