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.
From: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof
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.”
— wookieb (@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
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);
}
}
Is instanceof
bad then?
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.