Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

instanceof using ES6 class Inheritance chain doesn't work

Using ES6 class syntax, I'm wondering why the instanceof operator doesn't work for the inheritance chain when there are more than one chain of inheritance?

(optional read)

How instanceof operator works?

In obj instanceof Constructor, the instanceof operator checks if the 'prototype' property of the Constructor function is present in the prototype chain of the obj. If it is present, return true. Otherwise, false.


In the following snippet, the BTError inherits from Error (1.) and SomeError extends from BTError (3.).
But as we can see from (4.), the instanceof operator results false for new SomeError() instanceof BTError which in my understanding should be 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

Question

Is there anything non-trivial which I'm unable to comprehend or is instanceof operator just acting weird?

like image 667
abhisekp Avatar asked Feb 06 '17 09:02

abhisekp


2 Answers

Focusing on the last piece of your example

You're converting this code using BabelJS to make it compatible

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

This is the transpiled version of the code above

'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

I think the problem stems from the _inherit method, that assigns to subClass.prototype not the superClass.prototype directly, but an object created by merging that and another set of default properties.

With this chain of prototypes the inheritance would work but the instanceof operator would fail to travel it by reference, and so you get false where you expect true.

Apparently, according to this bug report, it's a known and expected behaviour (ie. limitation) and a possible workaround is to use babel-plugin-transform-builtin-extend

like image 167
alebianco Avatar answered Oct 12 '22 02:10

alebianco


Had this problem with TypeScript. Solved it by adding the following in the class constructor after the super calls:

Object.setPrototypeOf(this, YOUR_CLASS_HERE.prototype);

Not sure if it helps you.

like image 44
T. Dayya Avatar answered Oct 12 '22 02:10

T. Dayya