Skip to main content

Чому оператор instanceof потрібен у JavaScript

instanceof перевіряє, чи є в ланцюгу прототипів об'єкта прототип конкретного конструктора.

Теорія

TL;DR

  • Як родовідне дерево: перевіряє не тільки батьківський клас, а всіх предків аж до Object
  • typeof дає категорію ("object", "function"); instanceof каже, до якого класу в ієрархії належить об'єкт
  • Використовуй для перевірки ієрархій класів; не використовуй для примітивів і об'єктів з iframes
  • arr instanceof Array не спрацює з iframes; для масивів є Array.isArray()

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

javascript
class Animal {} class Dog extends Animal {} const rex = new Dog(); console.log(rex instanceof Dog); // true - Dog.prototype є в ланцюгу console.log(rex instanceof Animal); // true - іде вгору, знаходить Animal.prototype console.log(rex instanceof Object); // true - доходить до кореня console.log(typeof rex); // "object" - ніякої інформації про ієрархію

typeof повертає "object" однаково для масивів, дат і екземплярів власних класів. instanceof іде по ланцюгу прототипів і показує, де саме об'єкт вписується в ієрархію класів.

Що typeof не може визначити

typeof у JavaScript покриває 7 категорій для примітивів і дає "object" або "function" для всього іншого. Масиви, дати, екземпляри власних класів - всі повертають "object". Відрізнити їх через typeof неможливо.

instanceof існує тому, що JS використовує прототипне успадкування (prototype-based inheritance) під час виконання програми. Статичної системи типів немає, тому перевірка "чи є X підтипом Y" відбувається в рантаймі через обхід ланцюга.

Як відбувається обхід ланцюга

Коли ти пишеш rex instanceof Animal, рушій бере rex.__proto__ і порівнює з Animal.prototype. Не збіглось - переходить до rex.__proto__.__proto__. І так далі до збігу або до null.

V8 (Chrome, Node.js) кешує пошук прототипів через приховані класи, тому повторні перевірки для об'єктів однакової форми працюють швидко. Firefox слідує специфікації ECMAScript напряму: якщо Constructor.prototype не є об'єктом, одразу повертає false.

Коли використовувати

  • DOM-події: event instanceof MouseEvent в обробниках
  • Обробка помилок: err instanceof TypeError перед різними гілками відновлення
  • Розгалуження за підкласами: guard-умови в методах, що очікують конкретний підклас
  • Не для примітивів: 'hello' instanceof String поверне false
  • Не для масивів з iframes: є Array.isArray()

Часті помилки

Перевірка рядкових примітивів

javascript
console.log('hello' instanceof String); // false

Рядкові літерали це примітиви, а не об'єкти String. instanceof не бачить авто-боксинг. Використовуй typeof str === 'string'.

Масиви з iframes

javascript
const iframe = document.createElement('iframe'); document.body.appendChild(iframe); const arr = new iframe.contentWindow.Array(); console.log(arr instanceof Array); // false - інший глобальний Array

Кожен iframe має власні копії вбудованих конструкторів. На практиці ця проблема виникає щоразу, коли передаєш дані між iframe і батьківською сторінкою. Фікс: Array.isArray(arr).

Підміна властивості constructor

javascript
const proto = {}; const obj = Object.create(proto); obj.constructor = Dog; // для instanceof це не має значення console.log(obj instanceof Dog); // false - ланцюг прототипів є головним

instanceof читає ланцюг прототипів, а не властивість constructor. Властивість можна перезаписати, але instanceof це ігнорує.

Перевизначення Symbol.hasInstance

javascript
class FakeDog { static [Symbol.hasInstance]() { return true; } } console.log({} instanceof FakeDog); // true - власна логіка, не ланцюг

Деякі бібліотеки це перевизначають. Якщо потрібна надійна перевірка ланцюга, використовуй Object.getPrototypeOf(obj) вручну.

Де зустрічається в реальних проектах

  • Express перевіряє req instanceof http.IncomingMessage у core-middleware
  • Обробка помилок у Node.js: if (err instanceof SyntaxError) перед відповіддю клієнту
  • React навмисно уникає instanceof для перевірки елементів і використовує duck-typing саме через проблему з cross-realm перевірками
  • Mocha перевіряє, що об'єкти assertion є екземплярами функцій

Питання на співбесіді

Q: Чим instanceof відрізняється від obj.constructor === Constructor?
A: Властивість constructor можна перезаписати. instanceof читає реальний ланцюг прототипів і не залежить від значення цієї властивості.

Q: Що станеться з об'єктами з iframes?
A: Перевірка поверне false. Кожен browsing context має власні вбудовані конструктори. Масив з iframe не є екземпляром Array батьківської сторінки. Для масивів є Array.isArray().

Q: Що таке Symbol.hasInstance?
A: Статичний метод класу, який змінює поведінку instanceof. Приклад: class Iterable { static [Symbol.hasInstance](obj) { return Symbol.iterator in obj; } }. Будь-який об'єкт з ітератором пройде instanceof Iterable. Деякі бібліотеки це використовують.

Q: Як реалізувати instanceof вручну?
A: Пройти ланцюг прототипів самостійно: function myInstanceOf(obj, Cons) { let proto = Object.getPrototypeOf(obj); while (proto) { if (proto === Cons.prototype) return true; proto = Object.getPrototypeOf(proto); } return false; }. Класичне питання на senior-рівні.

Приклади

Guard для ієрархії класів

javascript
class Shape {} class Circle extends Shape { constructor(radius) { super(); this.radius = radius; } } function getArea(shape) { if (!(shape instanceof Shape)) { throw new TypeError('Очікується екземпляр Shape'); } if (shape instanceof Circle) { return Math.PI * shape.radius ** 2; } return 0; } console.log(getArea(new Circle(5))); // 78.54 console.log(getArea({})); // кидає TypeError

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

Розгалуження за типом помилки

javascript
async function fetchData(url) { try { const res = await fetch(url); return await res.json(); } catch (err) { if (err instanceof TypeError) { console.error('Мережева помилка або невірний URL'); } else if (err instanceof SyntaxError) { console.error('Тіло відповіді не є валідним JSON'); } else { throw err; } } }

Цей патерн часто зустрічається в API-шарах. instanceof дозволяє розгалужуватись за типом помилки без парсингу рядка .message.

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

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

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

Дочитали статтю?