Перевірка на екземпляр класу
instanceof є основним способом перевірити, що об'єкт є екземпляром класу: оператор шукає Class.prototype у ланцюжку прототипів об'єкта. Саме тому він працює і з наслідуванням, і з вбудованими класами, а його обмеження зводяться до випадків, коли ланцюжок прототипів змінили вручну або об'єкт прийшов з іншого JavaScript-контексту.
Теорія
TL;DR
obj instanceof Classповертаєtrue, якщоClass.prototypeзустрічається в ланцюжку прототипівobj.- Оператор іде вгору ланцюжком, тому підтримує наслідування:
dog instanceof Animalдаєtrue. obj.constructor === Classперевіряє конструктор, але ламається, якщо перезаписалиprototype.constructor.Object.getPrototypeOf(obj) === Class.prototypeпрацює лише для прямого екземпляра і не враховує наслідування.- З вбудованими класами (
Array,Date,Error,Map)instanceofпрацює так само, бо вони теж побудовані на прототипах. - Між контекстами (iframe, Worker) конструктори різні, тому
instanceofможе датиfalse; для масивів берітьArray.isArray().
Швидкий приклад
class User {}
const u = new User();
console.log(u instanceof User); // trueЯк instanceof працює під капотом
Вираз obj instanceof Class приблизно еквівалентний такому коду:
let proto = Object.getPrototypeOf(obj);
while (proto) {
if (proto === Class.prototype) return true;
proto = Object.getPrototypeOf(proto);
}
return false;Тобто instanceof просто перевіряє наявність Class.prototype у ланцюжку __proto__. Ніякої «магії типів» тут немає: це звичайний обхід зв'язаного списку прототипів.
Наслідування і ланцюжок прототипів
Оскільки оператор іде вгору ланцюжком, він бачить усіх предків класу:
class Animal {}
class Dog extends Animal {}
const dog = new Dog();
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true, наслідування працює
console.log(dog instanceof Object); // true, усі об'єкти наслідують від ObjectГлибше наслідування поводиться так само:
class A {}
class B extends A {}
class C extends B {}
const c = new C();
console.log(c instanceof A); // true
console.log(c instanceof B); // true
console.log(c instanceof C); // trueЛанцюжок прототипів тут виглядає так:
c -> C.prototype -> B.prototype -> A.prototype -> Object.prototype -> nullinstanceof послідовно піднімається цим ланцюжком, доки не знайде збіг або не дійде до null.
Альтернативи: constructor, getPrototypeOf і статичний метод
Можна перевірити, чи збігається конструктор об'єкта з класом:
class User {}
const u = new User();
console.log(u.constructor === User); // trueАле цей спосіб ламається у двох випадках: якщо об'єкт створили через Object.create() з іншим прототипом, або якщо хтось змінив prototype.constructor. Ось приклад, де обидві перевірки ще збігаються, бо ланцюжок прототипів цілий:
const fake = Object.create(User.prototype);
console.log(fake.constructor === User); // true
console.log(fake instanceof User); // trueОбидва способи працюють, доки ланцюжок прототипів не зламаний.
Третій варіант, це пряме порівняння прототипу:
class User {}
const u = new User();
console.log(Object.getPrototypeOf(u) === User.prototype); // trueВін працює тільки для прямого екземпляра: якщо є наслідування, метод не підніметься вище ланцюжком, на відміну від instanceof.
У великих кодових базах перевірку часто виносять у статичний метод самого класу, щоб логіка була в одному місці:
class User {
static isUser(obj) {
return obj instanceof User;
}
}
const u = new User();
console.log(User.isUser(u)); // trueВбудовані класи та різні контексти
З вбудованими класами оператор працює точно так само, адже вони теж побудовані на прототипах:
console.log([] instanceof Array); // true
console.log([] instanceof Object); // true
console.log(new Date() instanceof Date); // true
console.log(new Date() instanceof Object); // trueПроблема з'являється, коли об'єкт створено в іншій «реальності», наприклад в іншому iframe або в Worker. Там свої глобальні об'єкти, тому Array чи Object є іншими конструкторами, і перевірка може дати false:
// arr прийшов з іншого iframe
console.log(arr instanceof window.Array); // falseРішення для масивів, це вбудована перевірка, яка не залежить від контексту:
Array.isArray(arr); // безпечно навіть між контекстамиПідсумкова таблиця
| Спосіб | Приклад | Що перевіряє | Підтримує наслідування | Примітка |
|---|---|---|---|---|
instanceof | obj instanceof Class | Ланцюжок __proto__ | Так | Надійний варіант за замовчуванням |
constructor | obj.constructor === Class | Конструктор об'єкта | Частково | Ламається при зміненому prototype |
Object.getPrototypeOf | Object.getPrototypeOf(obj) === Class.prototype | Прямий прототип | Ні | Тільки для прямого класу |
| Статичний метод | Class.isInstance(obj) | Будь-яка логіка | Так | Зручно у великих проєктах |
Типові помилки
- Вважати
instanceofперевіркою типу. Це перевірка ланцюжка прототипів, а не типу значення. Для примітивів вона завжди даєfalse:'text' instanceof Stringповертаєfalse. - Використовувати
instanceof Arrayдля даних із чужого контексту. Між iframe і Worker конструктори різні, тому тут потрібенArray.isArray(). - Покладатися на
obj.constructor, коли клас міг перезаписатиprototype. Після ручного призначенняClass.prototype = {}властивістьconstructorгубиться і перевірка дає хибний результат. - Плутати
Object.getPrototypeOf(obj) === Class.prototypeзinstanceof. Перше не бачить предків, тому екземпляр підкласу таку перевірку не пройде. - Забувати, що
instanceofне працює зnullіundefined. Такі значення не мають прототипу, тож результат завждиfalse.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.