How Symbol.hasInstance Overrides instanceof in JS
In JavaScript, the instanceof operator traditionally
verifies if an object inherits from a constructor’s prototype chain.
With the introduction of ES6, JavaScript exposed the
Symbol.hasInstance well-known symbol, allowing developers
to directly customize and override the behavior of the
instanceof operator. This article explains how
Symbol.hasInstance works internally, how to implement it in
your classes and objects, and practical scenarios for customizing
instance checks.
How the
instanceof Operator Works Internally
Historically, evaluating obj instanceof Constructor
caused the JavaScript engine to traverse obj’s prototype
chain to check if Constructor.prototype was present.
In ES6 and later, the syntax left instanceof Right is
translated into a method call on the right-hand operand:
Right[Symbol.hasInstance](left)If the method exists, its return value determines the outcome of the
instanceof expression. If it is omitted, the engine falls
back to default prototype chain traversal using
Function.prototype[Symbol.hasInstance].
Overriding
instanceof in ES6 Classes
To customize instanceof validation inside an ES6 class,
define a static method using the computed property name
[Symbol.hasInstance]. This method receives the value on the
left side of the instanceof operator as its sole argument
and must return a boolean value.
class EvenNumber {
static [Symbol.hasInstance](instance) {
return typeof instance === 'number' && instance % 2 === 0;
}
}
console.log(4 instanceof EvenNumber); // true
console.log(7 instanceof EvenNumber); // false
console.log('4' instanceof EvenNumber); // falseIn this example, EvenNumber is not instantiated using
the new keyword. Instead, the instanceof
operator checks if the value fulfills a mathematical condition rather
than checking prototype inheritance.
Using
Symbol.hasInstance on Plain Objects
Because Symbol.hasInstance is a symbol property, it can
also be attached to standard objects using
Object.defineProperty. This allows plain objects to serve
as the right-hand operand for instanceof checks without
defining a class or constructor function.
const ArrayLike = {
[Symbol.hasInstance](instance) {
return (
instance != null &&
typeof instance[Symbol.iterator] === 'function' &&
typeof instance.length === 'number'
);
}
};
console.log([1, 2, 3] instanceof ArrayLike); // true
console.log('hello' instanceof ArrayLike); // true
console.log({ a: 1 } instanceof ArrayLike); // falseCommon Use Cases
1. Structural Typing (Duck Typing)
Instead of enforcing strict class hierarchy, you can check whether an object implements specific properties or methods.
class Serializable {
static [Symbol.hasInstance](obj) {
return obj != null && typeof obj.toJSON === 'function';
}
}
const customData = {
id: 1,
toJSON() { return JSON.stringify(this); }
};
console.log(customData instanceof Serializable); // true2. Cross-Realm and Multi-Window Validation
When dealing with objects created in different execution contexts
(such as <iframe> elements or Node.js vm
contexts), built-in instanceof checks fail because
prototypes differ across realms. Customizing
Symbol.hasInstance allows you to validate types using
universal tags (like Symbol.toStringTag) rather than
fragile prototype references.
3. Custom Primitive Type Guards
Standard instanceof checks evaluate to
false for primitive types (e.g.,
42 instanceof Number is false). By
implementing Symbol.hasInstance, you can extend custom type
validation to include primitive values.
Key Considerations
- Boolean Coercion: The result returned by
[Symbol.hasInstance]()is automatically coerced to a boolean. - Non-Configurable on Functions:
Function.prototype[Symbol.hasInstance]is defined as non-writable and non-configurable, meaning you cannot directly overwrite it on existing built-in functions without subclassing or reassigning on custom objects. - Execution Context: When invoked,
thisinside[Symbol.hasInstance]refers to the constructor or object on the right side of theinstanceofoperator.