Is instanceof broken?
- Published
We all know what instanceof
does. By definition
The instanceof operator tests to see if the prototype property of a constructor appears anywhere in the prototype chain of an object. The return value is a boolean value.
Few examples:
class Foo {
constructor() {
this.property = 'test';
}
}
new Foo() instanceof Foo; // true
{
property: 'test'
}
instanceof
Foo; // false
Very simple and clear. But as always in reality
„In theory there is no difference between theory and practice. In practice there is.”
— Lucas Kuzynski (@wookiebpl) July 25, 2021
Yogi Berra
When I was developing @pallad/secret
and @pallad/config i found that CLI for config didn't handle instances of Secret
and
couldn't detect instances of Provider
. Both of those classes uses instanceof
for type check, which seamed to be
great
option.
The issue was that those modules were installed more than one time on my system. One was global installation and second
local from examples folder. Creating an instance from local module does not work properly with instanceof
operator
within a global module. Who would have thought?
I'm not alone in that problem. The chance of receiving multiple installations of the same module in the project is quite
high. Take a look at npm list
in some of your projects to see that.
The fix? Just define extra unique non-configurable and non-enumerable property in your object. \
const TYPE_KEY = '@type';
const TYPE = '@mylib/name/Foo';
class Foo {
constructor() {
Object.defineProperty(this, TYPE_KEY, {
value: TYPE,
configurable: false,
enumerable: false
});
}
static is(value: any): value is Foo {
return value instanceof Foo ||
typeof value === 'object' && value !== null &&
value[TYPE_KEY] === TYPE
}
}
You can always use predicates to simplify that or use library created just for this purpose: https://github.com/pallad-ts/type-check
import * as is from 'predicates';
const TYPE_KEY = '@type';
const TYPE = '@mylib/name/Foo';
const IS_TYPE = is.property(TYPE_KEY, is.strictEqual(TYPE));
class Foo {
constructor() {
Object.defineProperty(this, TYPE_KEY, {
value: TYPE,
configurable: false,
enumerable: false
});
}
static is(value: any): value is Foo {
return value instanceof Foo || IS_TYPE(value);
}
}
instanceof
bad then?
Is Absolutely not! It is a great tool for its job. When working on local project where all modules are guaranteed to be installed once in the system or in the project then it is completely fine. However when developing a library that is suppose to be used my many people in various places it is worth to think about it.