Let's break down the typeof
and instanceof
operators in JavaScript and then create a custom instanceof
implementation.
typeof
Operator
-
Implementation Principle: The
typeof
operator determines the primitive type of a value. It's a unary operator (takes one operand) and returns a string indicating the type. Internally, it checks the internal [[Type]] property of the value. This property isn't directly accessible to JavaScript code, buttypeof
provides a way to query it. -
Limitations:
typeof
is primarily useful for primitive types. For objects (including arrays and functions), it usually returns "object," except for functions where it returns "function." This makes it less helpful for distinguishing between different object types.typeof null
notoriously returns "object," which is a historical bug.
instanceof
Operator
-
Implementation Principle: The
instanceof
operator checks if an object is an instance of a particular constructor (or a constructor in its prototype chain). It works by traversing the prototype chain of the object. If it encounters a prototype that matches the constructor's prototype, it returnstrue
. Otherwise, if it reaches the end of the prototype chain without a match, it returnsfalse
. -
How it works with prototypes: Every object in JavaScript has a prototype (accessible via
__proto__
orObject.getPrototypeOf()
). When you create an object using a constructor function (e.g.,new MyClass()
), the object's prototype is set to the constructor'sprototype
property.instanceof
leverages this relationship to determine inheritance.
Custom instanceof
Implementation
function myInstanceof(obj, constructor) {
if (obj === null || typeof obj !== 'object') {
return false; // null and non-objects are not instances of anything
}
let proto = Object.getPrototypeOf(obj);
while (proto) {
if (proto === constructor.prototype) {
return true;
}
proto = Object.getPrototypeOf(proto);
}
return false; // Reached end of prototype chain without a match
}
// Example usage:
function MyClass() {}
const myObj = new MyClass();
console.log(myInstanceof(myObj, MyClass)); // true
console.log(myInstanceof({}, Object)); // true
console.log(myInstanceof([], Array)); // true
console.log(myInstanceof([], Object)); // true (Arrays inherit from Object)
console.log(myInstanceof(null, Object)); // false (Handles the null case)
console.log(myInstanceof(5, Number)); // false (Handles primitives)
console.log(myInstanceof(myObj, Array)); // false
// Demonstrating with a more complex prototype chain
function Parent() {}
function Child() {}
Child.prototype = Object.create(Parent.prototype);
const childObj = new Child();
console.log(myInstanceof(childObj, Child)); // true
console.log(myInstanceof(childObj, Parent)); // true (Inheritance)
Key improvements in the custom implementation:
- Handles
null
correctly: Explicitly checks fornull
and returnsfalse
. - Handles non-objects: Checks if the left-hand operand is an object. If not, it returns
false
. This prevents errors when used with primitives. - Uses
Object.getPrototypeOf()
: This is the preferred way to access the prototype. While__proto__
works, it's deprecated for general use.
This custom implementation closely mirrors the behavior of the built-in instanceof
operator, providing a clearer understanding of its underlying mechanism. Remember that built-in operators are generally optimized, so in performance-critical scenarios, using the native instanceof
is recommended. However, for understanding and learning, building a custom version is extremely valuable.