Class Checking: “instanceof”

Class Checking: “instanceof”

Class Checking: “instanceof”


The operator instanceof is targeted at checking whether an object belongs to a specific class. It also helps to take inheritance into account.

Now, let’s use it to build a polymorphic function, which treats arguments differently depending on their type.

The instanceof operator

The following syntax is used for the instanceof operator:

obj instanceof Class

It may return true in case the object belongs to the Class or the class inheriting from it.

Here is an example:

class Dog {}

let dog = new Dog();

// is it an object of Dog class?

console.log(dog instanceof Dog); // true

It can work with constructor functions, as well:

// instead of class

function Dog() {}

console.log(new Dog() instanceof Dog); // true

Also, it works with built-in classes, such as Array:

let arr = [1, 2, 3];

console.log(arr instanceof Array); // true

console.log(arr instanceof Object); // true

Please, take into consideration that arr belongs to the Object class. The reason is that Array prototypically inherits from Object.

As a rule, the instanceof studies the prototype chain for the check. It is also possible to apply a custom logic inside the static method Symbol.hasInstance.

The obj instanceof Class algorithm works as follows:

// setup instanceOf check that assumes that

// anything with canEat property is an animal

class Animal {

  static[Symbol.hasInstance](obj) {

    if (obj.eats) return true;

  }

}

let obj = {

  eats: true

};

console.log(obj instanceof Animal); // true: Animal[Symbol.hasInstance](obj) is called

So, if there exists a static method Symbol.hasInstance, it can be called Class[Symbol.hasInstance](obj). It may either return true or false.

But most of the cases don’t include Symbol.hasInstance. In such cases, you need to follow the standard logic: obj instanceOf Class will check whether Class.prototype is the same as one of the prototypes in the obj prototype chain.

Here is an example:

obj.__proto__ === Class.prototype ?
  obj.__proto__.__proto__ === Class.prototype ?
  obj.__proto__.__proto__.__proto__ === Class.prototype ?
  ...
// if any answer is true, return true
// otherwise, if we reach the end of the chain, return false

In the example given above dog.__proto__ === Dog.prototype, the answer is given immediately.

If there is an inheritance, the match is at the second step, as follows:

class Animal {}

class Dog extends Animal {}

let dog = new Dog();

console.log(dog instanceof Dog); // true

// dog.__proto__ === Dog.prototype

// dog.__proto__.__proto__ === Animal.prototype

Let’s take a look at the illustrations below:

Another method can be highlighted. It’s objA.isPrototypeOf(objB), which returns true in case objA is inside the chain of prototypes and Class.prototype matters.

It may lead to extraordinary results when a prototype property is transformed after the object is generated.

Here is an example:

function Dog() {}

let dog = new Dog();

// changed the prototype

Dog.prototype = {};

// not a dog any more!

console.log(dog instanceof Dog); // false

Bonus: Object.prototype.toString for the type

As it was already noted, plain objects can be converted to a string as [object Object], like here:

let obj = {};

console.log(obj); // [object Object]

console.log(obj.toString()); // the same

It’s their implementation of toString. But, there exists a hidden feature, making toString more robust than that. It may be used as an extended typeof, as well as an alternative for instanceof.

The built-in toString may be extracted from the object, as well as executed in any other value’s context. The result of it depends on that value.

  • It can be [object Number] for a number.
  • It can be [object Boolean] for a boolean.
  • It can be [object Null] for null.
  • It can be [object Undefined] for undefined.
  • [object Array] : for arrays.

It’s demonstrated in the example below:

// copy toString method into a variable for convenience

let objToString = Object.prototype.toString;

let arr = []; // what type is this?

console.log(objToString.call(arr)); // [object Array]

The toString algorithm explores this returning the corresponding result.

Here is another example:

let str = Object.prototype.toString;

console.log(str.call(123)); // [object Number]

console.log(str.call(null)); // [object Null]

console.log(str.call(alert)); // [object Function]

Symbol.toStringTag

The Object toString behavior may be customized, applying a unique object property that is Symbol.toStringTag.

The example is as follows:

let animal = {

  [Symbol.toStringTag]: "Animal"

};

console.log({}.toString.call(animal)); // [object Animal]

There is such property for most of the environment-specific objects. For instance:

// toStringTag for the environment-specific object and class:

console.log(window[Symbol.toStringTag]); // window

console.log(XMLHttpRequest.prototype[Symbol.toStringTag]); // XMLHttpRequest

console.log({}.toString.call(window)); // [object Window]

console.log({}.toString.call(new XMLHttpRequest())); // [object XMLHttpRequest]

So, you can notice that the result is Symbol.toStringTag that is wrapped into [object ...].

The {}.toString.call may be used instead of instanceof for built-in objects whenever it’s necessary to receive the type as a string rather than just check.

Reactions

Post a Comment

0 Comments

close