Проверка типа: instanceof

прототипы≈ 7 минут

Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.

Оператор instanceof не заглядывает в свидетельство о рождении объекта — он идёт по цепочке прототипов и на каждом шаге спрашивает: «а это не ты?». Разберём, где он точнее typeof, где подводит и почему массивы проверяют совсем иначе.

Разделы темы:·

Запись obj instanceof Cat читается так: «встречается ли объект Cat.prototype в цепочке прототипов obj?». Движок берёт прототип объекта, сравнивает, не совпало — поднимается на уровень выше, и так до null. Ответ всегда булев: true или false.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Наследование поэтому работает бесплатно: у Cat.prototype в цепочке стоит Animal.prototype, и оба ответа — true. Справа при этом стоит не сам класс, а его свойство prototype: именно этот объект ищут в цепочке.

Отсюда главное следствие: instanceof ничего не знает о том, кто на самом деле создал объект. Он смотрит только на текущую цепочку — а её можно переписать на ходу.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

typeof отвечает на вопрос «какого сорта это значение» и возвращает строку. Он работает с чем угодно, включая примитивы, но различает всего восемь вариантов — и все объекты, кроме функций, сваливает в одну кучу с меткой object. Там же по историческому недоразумению оказался null, который объектом вовсе не является.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

instanceof начинается там, где typeof заканчивается: он различает объекты по происхождению. Но обратная сторона — он бесполезен для примитивов. Литерал строки не объект, цепочки прототипов у него нет; объект-обёртка возникает только на время вызова метода и тут же исчезает.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
Значениеtypeofinstanceof Object
42numberfalse
«текст»stringfalse
[1, 2]objecttrue
nullobjectfalse
function () {}functiontrue
new Map()objecttrue

Массив — объект, и typeof честно говорит object. instanceof Array ответ даёт, но у него есть слабое место: каждое окно, iframe и воркер имеют собственный набор встроенных классов. Массив, приехавший из соседнего окна, наследует от чужого Array.prototype — и проверка внезапно возвращает false, хотя перед вами настоящий массив.

Array.isArray смотрит на внутреннюю метку значения, а не на прототипы, поэтому отвечает правильно всегда. Правило простое: массивы проверяем через Array.isArray, всё остальное — через instanceof.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Старый приём: Object.prototype.toString достаёт ту самую внутреннюю метку и отдаёт её строкой. Он тоже не зависит от цепочки прототипов и различает то, что typeof путает.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

В catch прилетает всё подряд: и ожидаемая ошибка разбора данных, и опечатка в имени переменной. Разделить их — прямая работа для instanceof: встроенные классы ошибок образуют иерархию с общим предком Error.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Свой класс ошибки наследуют от Error — и вся иерархия сразу становится проверяемой. Поле name задают вручную (полем класса или в конструкторе): без него ошибка будет представляться просто как Error. Здесь важна только проверяемость иерархии; как такие классы устроены внутри — с cause, доменными полями и общим предком на всё семейство — разбирает статья «Свои классы ошибок».

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

В цепочке проверок идите от узкого к широкому: ветка с instanceof Error, поставленная первой, перехватит вообще всё.

instanceof — не зашитая намертво магия. Прежде чем идти по прототипам, движок ищет у правого операнда метод Symbol.hasInstance; если тот есть, оператор просто вызывает его и приводит результат к булеву. Цепочка прототипов при этом не трогается вовсе.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

В обычном коде такое почти не нужно, но знать полезно по двум причинам. Во-первых, это объясняет странное поведение чужой библиотеки: проверку могли переопределить. Во-вторых, видно, что справа обязан стоять объект, который умеет отвечать на вопрос, — иначе будет не false, а исключение.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Объект с полем prototype не подходит: без Symbol.hasInstance оператор требует справа функцию. Поэтому instanceof нельзя применить к обычному объекту-образцу — только к классу, функции-конструктору или к тому, кто явно описал своё поведение.

  • obj instanceof Cls ищет объект Cls.prototype в цепочке прототипов obj — это вопрос о цепочке, а не о том, кто создал объект.
  • typeof различает сорта значений и работает с примитивами; instanceof различает объекты по происхождению и на примитивах по умолчанию даёт false — пока проверку не переопределили через Symbol.hasInstance.
  • Массивы проверяют через Array.isArray: instanceof Array ломается на значениях из другого окна или iframe.
  • Свои классы ошибок наследуют от Error, а в catch проверяют от узкого класса к широкому.
  • Symbol.hasInstance позволяет объекту самому отвечать на instanceof; без него справа обязана стоять функция, иначе будет TypeError.

1/3Что выведет console.log("текст" instanceof String)?

2/3Почему для массивов советуют Array.isArray, а не instanceof Array?

3/3class Even { static [Symbol.hasInstance](v) { return v % 2 === 0; } } — что выведет console.log(3 instanceof Even)?

Разложить ошибки по типам

Дана иерархия ошибок приложения. Напишите функцию explain, которая по объекту ошибки возвращает понятную строку. Чужие ошибки (не наследники AppError) должны попадать в общую ветку.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Ключевое — порядок проверок. NotFoundError является и AppError, и Error, поэтому широкие ветки ставим последними: иначе они перехватят всё.

jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

Заметьте: instanceof AppError не пришлось дублировать для наследников — цепочка прототипов делает это за нас.

Свой ответ на instanceof

Доработайте объект PositiveNumber так, чтобы проверка instanceof отвечала true только для положительных чисел. Строка "7" положительным числом не считается.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Достаточно добавить метод Symbol.hasInstance — тогда объект отвечает на вопрос сам, и требование «справа должна быть функция» отпадает.

jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

Проверка typeof value === "number" обязательна: без неё сравнение "7" > 0 дало бы true из-за приведения типов.