This commit is contained in:
Ilya Kantor 2019-08-04 17:36:26 +03:00
parent f96872425d
commit 6d1fa5de73
8 changed files with 84 additions and 79 deletions

View file

@ -11,7 +11,7 @@ The syntax is:
obj instanceof Class
```
It returns `true` if `obj` belongs to the `Class` (or a class inheriting from it).
It returns `true` if `obj` belongs to the `Class` or a class inheriting from it.
For instance:
@ -46,15 +46,17 @@ 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, but we can set a custom logic in the static method `Symbol.hasInstance`.
Normally, `instanceof` operator examines the prototype chain for the check. We can also set a custom logic in the static method `Symbol.hasInstance`.
The algorithm of `obj instanceof Class` works roughly as follows:
1. If there's a static method `Symbol.hasInstance`, then just call it: `Class[Symbol.hasInstance](obj)`. It should return either `true` or `false`. We're done.
For example:
1. If there's a static method `Symbol.hasInstance`, then just call it: `Class[Symbol.hasInstance](obj)`. It should return either `true` or `false`, and we're done. That's how we can customize the behavior of `instanceof`.
For example:
```js run
// setup instanceOf check that assumes that anything that canEat is an animal
// setup instanceOf check that assumes that
// anything with canEat property is an animal
class Animal {
static [Symbol.hasInstance](obj) {
if (obj.canEat) return true;
@ -68,17 +70,19 @@ The algorithm of `obj instanceof Class` works roughly as follows:
2. Most classes do not have `Symbol.hasInstance`. In that case, the standard logic is used: `obj instanceOf Class` checks whether `Class.prototype` equals to one of prototypes in the `obj` prototype chain.
In other words, compare:
In other words, compare one after another:
```js
obj.__proto__ === Class.prototype
obj.__proto__.__proto__ === Class.prototype
obj.__proto__.__proto__.__proto__ === Class.prototype
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 reached the end of the chain, return false
```
In the example above `Rabbit.prototype === rabbit.__proto__`, so that gives the answer immediately.
In the example above `rabbit.__proto__ === Rabbit.prototype`, so that gives the answer immediately.
In the case of an inheritance, `rabbit` is an instance of the parent class as well:
In the case of an inheritance, the match will be at the second step:
```js run
class Animal {}
@ -88,8 +92,11 @@ The algorithm of `obj instanceof Class` works roughly as follows:
*!*
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`:
@ -100,7 +107,7 @@ By the way, there's also a method [objA.isPrototypeOf(objB)](mdn:js/object/isPro
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.
That can lead to interesting consequences when `prototype` property is changed after the object is created.
Like here:
@ -117,8 +124,6 @@ alert( rabbit instanceof Rabbit ); // false
*/!*
```
That's one of the reasons to avoid changing `prototype`. Just to keep safe.
## Bonus: Object.prototype.toString for the type
We already know that plain objects are converted to string as `[object Object]`:
@ -152,7 +157,7 @@ let objectToString = Object.prototype.toString;
// what type is this?
let arr = [];
alert( objectToString.call(arr) ); // [object Array]
alert( objectToString.call(arr) ); // [object *!*Array*/!*]
```
Here we used [call](mdn:js/function/call) as described in the chapter [](info:call-apply-decorators) to execute the function `objectToString` in the context `this=arr`.
@ -196,11 +201,11 @@ As you can see, the result is exactly `Symbol.toStringTag` (if exists), wrapped
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.
We can use `{}.toString.call` 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:
Let's summarize the type-checking methods that we know:
| | works for | returns |
|---------------|-------------|---------------|