instanceof с использованием цепочки наследования класса ES6 не работает

Используя синтаксис ES6 class, мне интересно, почему оператор instanceof не работает для цепочки наследования, когда существует более одной цепочки наследования?

(optional read)

Как работает оператор instanceof?

В obj instanceof Constructor оператор instanceof проверяет, присутствует ли свойство 'prototype' функции Constructor в цепочке prototype функции obj. Если он присутствует, вернуть true. В противном случае false.


В следующем фрагменте BTError наследуется от Error (1.), а SomeError расширяется от BTError (3.). ).
Но, как видно из (4.), оператор instanceof возвращает false вместо new SomeError() instanceof BTError, что, по моему мнению, должно быть true.

class BTError extends Error {}
console.log('1.', Reflect.getPrototypeOf(BTError.prototype) === Error.prototype); // 1. true
console.log('2.', new BTError() instanceof Error); // 2. true

console.log('');

class SomeError extends BTError {}
console.log('3.', Reflect.getPrototypeOf(SomeError.prototype) === BTError.prototype); // 3. true
console.log('4.', new SomeError() instanceof BTError); // 4. false

console.log('');

class SpecificError extends SomeError {}
console.log('5.', Reflect.getPrototypeOf(SpecificError.prototype) === SomeError.prototype); // 5. true
console.log('6.', new SpecificError() instanceof Error); // 6. true
console.log('7.', new SpecificError() instanceof BTError); // 7. false
console.log('8.', new SpecificError() instanceof SomeError); // 8. false


Вопрос

Есть ли что-то нетривиальное, что я не могу понять, или оператор instanceof просто ведет себя странно?


person abhisekp    schedule 06.02.2017    source источник
comment
Я получаю 8x true в FF, Edge и Chrome   -  person Thomas    schedule 06.02.2017
comment
@Thomas правда, вот скрипка jsfiddle.net/o93Lm0rc/321, она печатает 8 раз верно   -  person Vinod Louis    schedule 06.02.2017
comment
Попробуйте отключить BableJs из фрагмента, и вы получите все проверки true. Похоже транспиляция лажает...   -  person alebianco    schedule 06.02.2017
comment
@alebianco спасибо всем. Действительно, Babel.js ведет себя странно.   -  person abhisekp    schedule 06.02.2017
comment
@abhisekp я расширил свой комментарий в полномасштабном ответе после того, как немного покопался в проблеме.   -  person alebianco    schedule 06.02.2017
comment
связанный ответ: stackoverflow.com/a/33877501/896465   -  person seyed    schedule 11.06.2017


Ответы (3)


Сосредоточившись на последней части вашего примера

Вы конвертируете этот код с помощью BabelJS, чтобы сделать его совместимым

class BTError extends Error {}
class SomeError extends BTError {}
class SpecificError extends SomeError {}

console.log('6.', new SpecificError() instanceof Error);
console.log('7.', new SpecificError() instanceof BTError);
console.log('8.', new SpecificError() instanceof SomeError);

Это транспилированная версия кода выше

'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 BTError = function(_Error) {
    _inherits(BTError, _Error);

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

        return _possibleConstructorReturn(this, (BTError.__proto__ || Object.getPrototypeOf(BTError)).apply(this, arguments));
    }

    return BTError;
}(Error);

var SomeError = function(_BTError) {
    _inherits(SomeError, _BTError);

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

        return _possibleConstructorReturn(this, (SomeError.__proto__ || Object.getPrototypeOf(SomeError)).apply(this, arguments));
    }

    return SomeError;
}(BTError);

var SpecificError = function(_SomeError) {
    _inherits(SpecificError, _SomeError);

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

        return _possibleConstructorReturn(this, (SpecificError.__proto__ || Object.getPrototypeOf(SpecificError)).apply(this, arguments));
    }

    return SpecificError;
}(SomeError);

console.log('6.', new SpecificError() instanceof Error); // 6. true
console.log('7.', new SpecificError() instanceof BTError); // 7. false
console.log('8.', new SpecificError() instanceof SomeError); // 8. false

Я думаю, что проблема связана с методом _inherit, который присваивает subClass.prototype не superClass.prototype напрямую, а объект, созданный путем слияния этого и другого набора свойств по умолчанию.

С этой цепочкой прототипов наследование будет работать, но оператор instanceof не сможет перемещаться по нему по ссылке, и поэтому вы получите false там, где ожидали true.

Судя по этому отчету об ошибке, это известное и ожидаемое поведение (т.е. ограничение) и возможный обходной путь — использовать babel-plugin-transform-builtin-extend

person alebianco    schedule 06.02.2017

Была эта проблема с TypeScript. Решил это, добавив следующее в конструктор класса после супервызовов:

Object.setPrototypeOf(this, YOUR_CLASS_HERE.prototype);

Не уверен, что это поможет вам.

person T. Dayya    schedule 25.04.2020

В моем случае instanceof не работал с целью компиляции, установленной на es5. При изменении на es6 он дал правильный результат.

person belvederef    schedule 28.09.2020