Class checking: "instanceof"
The instanceof
operator allows to check whether an object belongs to a certain class. It also takes inheritance into account.
Such a check may be necessary in many cases, here we'll use it for building a polymorphic function, the one that treats arguments differently depending on their type.
The instanceof operator
The syntax is:
obj instanceof Class
It returns true
if obj
belongs to the Class
(or a class inheriting from it).
For instance:
class Rabbit {}
let rabbit = new Rabbit();
// is it an object of Rabbit class?
*!*
alert( rabbit instanceof Rabbit ); // true
*/!*
It also works with constructor functions:
*!*
// instead of class
function Rabbit() {}
*/!*
alert( new Rabbit() instanceof Rabbit ); // true
...And with built-in classes like Array
:
let arr = [1, 2, 3];
alert( arr instanceof Array ); // true
alert( arr instanceof Object ); // true
Please note that arr
also belongs to the Object
class. That's because Array
prototypally inherits from Object
.
The instanceof
operator examines the prototype chain for the check, and is also fine-tunable using the static method Symbol.hasInstance
.
The algorithm of obj instanceof Class
works roughly as follows:
-
If there's a static method
Symbol.hasInstance
, then use it. Like this:// assume anything that canEat is an animal class Animal { static [Symbol.hasInstance](obj) { if (obj.canEat) return true; } } let obj = { canEat: true }; alert(obj instanceof Animal); // true: Animal[Symbol.hasInstance](obj) is called
-
Most classes do not have
Symbol.hasInstance
. In that case, check ifClass.prototype
equals to one of prototypes in theobj
prototype chain.In other words, compare:
obj.__proto__ === Class.prototype obj.__proto__.__proto__ === Class.prototype obj.__proto__.__proto__.__proto__ === Class.prototype ...
In the example above
Rabbit.prototype === rabbit.__proto__
, so that gives the answer immediately.In the case of an inheritance,
rabbit
is an instance of the parent class as well:class Animal {} class Rabbit extends Animal {} let rabbit = new Rabbit(); *!* alert(rabbit instanceof Animal); // true */!* // rabbit.__proto__ === Rabbit.prototype // rabbit.__proto__.__proto__ === Animal.prototype (match!)
Here's the illustration of what rabbit instanceof Animal
compares with Animal.prototype
:
By the way, there's also a method objA.isPrototypeOf(objB), that returns true
if objA
is somewhere in the chain of prototypes for objB
. So the test of obj instanceof Class
can be rephrased as Class.prototype.isPrototypeOf(obj)
.
That's funny, but the Class
constructor itself does not participate in the check! Only the chain of prototypes and Class.prototype
matters.
That can lead to interesting consequences when prototype
is changed.
Like here:
function Rabbit() {}
let rabbit = new Rabbit();
// changed the prototype
Rabbit.prototype = {};
// ...not a rabbit any more!
*!*
alert( rabbit instanceof Rabbit ); // false
*/!*
That's one of the reasons to avoid changing prototype
. Just to keep safe.
Bonus: Object toString for the type
We already know that plain objects are converted to string as [object Object]
:
let obj = {};
alert(obj); // [object Object]
alert(obj.toString()); // the same
That's their implementation of toString
. But there's a hidden feature that makes toString
actually much more powerful than that. We can use it as an extended typeof
and an alternative for instanceof
.
Sounds strange? Indeed. Let's demystify.
By specification, the built-in toString
can be extracted from the object and executed in the context of any other value. And its result depends on that value.
-
For a number, it will be
[object Number]
-
For a boolean, it will be
[object Boolean]
-
For
null
:[object Null]
-
For
undefined
:[object Undefined]
-
For arrays:
[object Array]
- ...etc (customizable).
Let's demonstrate:
// copy toString method into a variable for convenience
let objectToString = Object.prototype.toString;
// what type is this?
let arr = [];
alert( objectToString.call(arr) ); // [object Array]
Here we used call as described in the chapter to execute the function objectToString
in the context this=arr
.
Internally, the toString
algorithm examines this
and returns the corresponding result. More examples:
let s = Object.prototype.toString;
alert( s.call(123) ); // [object Number]
alert( s.call(null) ); // [object Null]
alert( s.call(alert) ); // [object Function]
Symbol.toStringTag
The behavior of Object toString
can be customized using a special object property Symbol.toStringTag
.
For instance:
let user = {
[Symbol.toStringTag]: "User"
};
alert( {}.toString.call(user) ); // [object User]
For most environment-specific objects, there is such a property. Here are few browser specific examples:
// toStringTag for the envinronment-specific object and class:
alert( window[Symbol.toStringTag]); // window
alert( XMLHttpRequest.prototype[Symbol.toStringTag] ); // XMLHttpRequest
alert( {}.toString.call(window) ); // [object Window]
alert( {}.toString.call(new XMLHttpRequest()) ); // [object XMLHttpRequest]
As you can see, the result is exactly Symbol.toStringTag
(if exists), wrapped into [object ...]
.
At the end we have "typeof on steroids" that not only works for primitive data types, but also for built-in objects and even can be customized.
It can be used instead of instanceof
for built-in objects when we want to get the type as a string rather than just to check.
Summary
Let's recap the type-checking methods that we know:
|
works for | returns |
---|---|---|
|
primitives |
|
|
primitives, built-in objects, objects with Symbol.toStringTag |
|
|
objects |
|
As we can see,
is technically a "more advanced" typeof.{}.toString
And instanceof operator really shines when we are working with a class hierarchy and want to check for the class taking into account inheritance.