Оператор instanceof дозволяє перевірити, чи належить об’єкт до певного класу. Він також враховує наслідування.
Така перевірка може знадобитися в багатьох випадках. Наприклад, його можна використати для створення поліморфної функції, яка обробляє аргументи по-різному залежно від їх типу.
Оператор instanceof
Синтаксис такий:
obj instanceof Class
Він повертає true, якщо obj належить до класу Class або класу, який наслідується від нього.
Наприклад:
Він також працює з функціями-конструкторами:
…І з вбудованими класами як Array:
Будь ласка, зверніть увагу, що arr також належить до класу Object. Це тому, що клас Array прототипно наслідується від Object.
Зазвичай, instanceof перевіряє ланцюжок прототипів. Ми також можемо задати будь-яку спеціальну логіку в статичному методі Symbol.hasInstance, і замінити звичайну поведінку.
Алгоритм операції obj instanceof Class працює приблизно наступним чином:
-
Якщо є статичний метод
Symbol.hasInstance, тоді він просто викликаєтсья:Class[Symbol.hasInstance](obj). Він повинен повернутиtrueабоfalse, ось і все. Ось як ми можемо задати поведінкуinstanceof.Наприклад:
-
Більшість класів не мають
Symbol.hasInstance. У цьому випадку використовується стандартна логіка:obj instanceOf Classперевіряє чиClass.prototypeдорівнює одному з прототипів у ланцюжку прототипівobj.Іншими словами, прототипи порівнюються один за одним:
obj.__proto__ === Class.prototype? obj.__proto__.__proto__ === Class.prototype? obj.__proto__.__proto__.__proto__ === Class.prototype? ... // Якщо будь-яке з них буде true, то instanceof одразу ж верне true. // Якщо ми досягли кінця ланцюжка - повертається falseУ наведеному вище прикладі
rabbit.__proto__ === Rabbit.prototype, тому ми знаходимо відповідь негайно.У разі наслідування ми знайдемо те, що шукали, на другому кроці:
Ось ілюстрація того, як операція rabbit instanceof Animal шукає Animal.prototype у прототипах:
До речі, є також метод objA.isPrototypeOf(objB), який повертає true якщо objA знаходиться десь у ланцюжку прототипів для objB. Отже, перевірку obj instanceof Class можна замінити на Class.prototype.isPrototypeOf(obj).
Цікаво, але сам класс Class не бере участі в перевірці! Має значення лише ланцюжок прототипів і Class.prototype.
Це може призвести до дивних наслідків, коли властивість prototype було змінено після створення об’єкта.
Як тут:
Бонус: Object.prototype.toString для визначення типу
Ми вже знаємо, що прості об’єкти перетворюються на рядки як [object Object]:
Це їх реалізація метода toString. Але є прихована функція, яка робить метод toString набагато потужнішим. Ми можемо використовувати його як розширений typeof і альтернативу instanceof.
Звучить дивно? Дійсно. Давайте розбиратися.
У специфікації, вбудований метод toString можна витягнути з об’єкта та викликати в контексті будь-якого іншого значення. І результат залежить від типу цього значення.
- Для числа це буде
[object Number] - Для логічного значення це буде
[object Boolean] - Для
null:[object Null] - Для
undefined:[object Undefined] - Для масивів:
[object Array] - …тощо.
Давайте продемонструємо:
Тут ми використали call, як описано в розділі Декоратори та переадресація виклику, call/apply, щоб викликати функцію objectToString з контекстом this=arr.
Всередені алгоритм toString перевіряє this і повертає відповідний результат. Більше прикладів:
Symbol.toStringTag
Поведінку методу об’єкта toString можна налаштувати за допомогою спеціальної властивості Symbol.toStringTag.
Наприклад:
Для більшості специфічних для середовища об’єктів така властивість є. Ось деякі приклади для браузера:
// toStringTag для специфічних для середовища об’єкту та класу:
alert( window[Symbol.toStringTag]); // Window
alert( XMLHttpRequest.prototype[Symbol.toStringTag] ); // XMLHttpRequest
alert( {}.toString.call(window) ); // [object Window]
alert( {}.toString.call(new XMLHttpRequest()) ); // [object XMLHttpRequest]
Як бачите, результатом є саме Symbol.toStringTag (якщо існує), загорнутий у [object ...].
Наприкінці ми маємо “typeof на стероїдах”, який працює не тільки для примітивних типів даних, але й для вбудованих об’єктів і навіть може бути кастомізований.
Ми можемо використати {}.toString.call замість instanceof для вбудованих об’єктів, коли ми хочемо отримати тип у вигляді рядка, а не просто для перевірки.
Підсумки
Давайте підсумуємо відомі нам методи перевірки типів:
| працює для | повертає | |
|---|---|---|
typeof |
примітивів | рядок |
{}.toString |
примітивів, вбудованих об’єктів, об’єктів з Symbol.toStringTag |
рядок |
instanceof |
об’єктів | true/false |
Як ми бачимо, {}.toString технічно є “більш просунутим” typeof.
І оператор instanceof дійсно сяє, коли ми працюємо з ієрархією класів і хочемо перевірити клас з урахуванням наслідування.