Skip to main content

Перевірка на екземпляр класу

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().

Швидкий приклад

javascript
class User {} const u = new User(); console.log(u instanceof User); // true

Як instanceof працює під капотом

Вираз obj instanceof Class приблизно еквівалентний такому коду:

javascript
let proto = Object.getPrototypeOf(obj); while (proto) { if (proto === Class.prototype) return true; proto = Object.getPrototypeOf(proto); } return false;

Тобто instanceof просто перевіряє наявність Class.prototype у ланцюжку __proto__. Ніякої «магії типів» тут немає: це звичайний обхід зв'язаного списку прототипів.

Наслідування і ланцюжок прототипів

Оскільки оператор іде вгору ланцюжком, він бачить усіх предків класу:

javascript
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

Глибше наслідування поводиться так само:

javascript
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

Ланцюжок прототипів тут виглядає так:

javascript
c -> C.prototype -> B.prototype -> A.prototype -> Object.prototype -> null

instanceof послідовно піднімається цим ланцюжком, доки не знайде збіг або не дійде до null.

Альтернативи: constructor, getPrototypeOf і статичний метод

Можна перевірити, чи збігається конструктор об'єкта з класом:

javascript
class User {} const u = new User(); console.log(u.constructor === User); // true

Але цей спосіб ламається у двох випадках: якщо об'єкт створили через Object.create() з іншим прототипом, або якщо хтось змінив prototype.constructor. Ось приклад, де обидві перевірки ще збігаються, бо ланцюжок прототипів цілий:

javascript
const fake = Object.create(User.prototype); console.log(fake.constructor === User); // true console.log(fake instanceof User); // true

Обидва способи працюють, доки ланцюжок прототипів не зламаний.

Третій варіант, це пряме порівняння прототипу:

javascript
class User {} const u = new User(); console.log(Object.getPrototypeOf(u) === User.prototype); // true

Він працює тільки для прямого екземпляра: якщо є наслідування, метод не підніметься вище ланцюжком, на відміну від instanceof.

У великих кодових базах перевірку часто виносять у статичний метод самого класу, щоб логіка була в одному місці:

javascript
class User { static isUser(obj) { return obj instanceof User; } } const u = new User(); console.log(User.isUser(u)); // true

Вбудовані класи та різні контексти

З вбудованими класами оператор працює точно так само, адже вони теж побудовані на прототипах:

javascript
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:

javascript
// arr прийшов з іншого iframe console.log(arr instanceof window.Array); // false

Рішення для масивів, це вбудована перевірка, яка не залежить від контексту:

javascript
Array.isArray(arr); // безпечно навіть між контекстами

Підсумкова таблиця

СпосібПрикладЩо перевіряєПідтримує наслідуванняПримітка
instanceofobj instanceof ClassЛанцюжок __proto__ТакНадійний варіант за замовчуванням
constructorobj.constructor === ClassКонструктор об'єктаЧастковоЛамається при зміненому prototype
Object.getPrototypeOfObject.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.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.