JS Tip of the Day: Symbol.hasInstance

Symbol.hasInstance
Version: ES2015
Level: Advanced

By default, the instanceof operator checks an object to see if it belongs to a constructor by checking the prototype chain of the object. If the prototype chain has an object matching the prototype property of the constructor, instanceof returns true.

let array = new Array();
console.log(array instanceof Array); // true
console.log(array instanceof Object); // true

let object = new Object();
console.log(object instanceof Array); // false
console.log(object instanceof Object); // true

This behavior can be customized using Symbol.hasInstance. When a constructor implements a static Symbol.hasInstance, that will be used to determine whether or not an object called with instanceof is an instance of that constructor overriding the standard prototype check.

class Faker {
    static [Symbol.hasInstance] (instance) {
        return instance.fakable;
    }
}
let normal = {};
let faked = { fakable: true };
console.log(normal instanceof Faker); // false
console.log(faked instanceof Faker); // true

One thing to be careful of is, because the Symbol.hasInstance method replaces the normal check, it could also return false positives; if an instance was actually created by a certain constructor, instanceof might not reflect that.

class Faker {
    static [Symbol.hasInstance] (instance) {
        return instance.fakable;
    }
}
let fakedFaker = new Faker();
console.log(fakedFaker instanceof Faker); // false

If you want access to the default behavior, you can call the Symbol.hasInstance method of Function.prototype. This way, if you want to fallback to the default behavior when your custom check fails, you have the option to do so.

class Faker {
    static [Symbol.hasInstance] (instance) {
        let origHas = Function.prototype[Symbol.hasInstance];
        return instance.fakable || origHas.call(this, instance);
    }
}
let fakedFaker = new Faker();
console.log(fakedFaker instanceof Faker); // true

Symbol.hasInstance can be useful with mixins. If your mixin is defined by a constructible function, that function can be used with instanceof to determine whether or not the mixin was applied to an instance used with instanceof

let eaterMixin = Symbol('eaterMixin');
function eater (base) {
    return class extends base {
        constructor (...args) {
            super(...args);
            this[eaterMixin] = true;
        }
        eat () {
            console.log('Nom nom');
        }
    }
}
Object.defineProperty(eater, Symbol.hasInstance, {
    value (instance) {
        return eaterMixin in instance;
    }
});
class Hippo {}
let EatingHippo = eater(Hippo);

let hungryHippo = new Hippo();
let chonkyHippo = new EatingHippo();
chonkyHippo.eat(); // Nom nom

console.log(hungryHippo instanceof Hippo); // true
console.log(hungryHippo instanceof eater); // false
console.log(chonkyHippo instanceof Hippo); // true
console.log(chonkyHippo instanceof eater); // true

Here, the eater mixin adds a symbol property to each instance it creates which is then used with the Symbol.hasInstance check. If that symbol doesn’t exist, its not an “instance of” the mixin.

Note that a symbol doesn’t have to be used for this check. You could also do something else to identify the mixin, such as check to see if the instance has the methods/properties defined by the mixin. Symbols are useful in that they’re unique and mostly non-intrusive.

Also worth noting is that to set Symbol.hasInstance, Object.defineProperty() was necessary because the default Symbol.hasInstance in Function.prototype is defined as not writable. Because of this a normal assignment would not work. Using define on the instance ignores the writable characteristic in the inherited value.

More info: