Проверка типа: instanceof
Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.
Оператор instanceof не заглядывает в свидетельство о рождении объекта — он идёт по цепочке прототипов и на каждом шаге спрашивает: «а это не ты?». Разберём, где он точнее typeof, где подводит и почему массивы проверяют совсем иначе.
Запись obj instanceof Cat читается так: «встречается ли объект Cat.prototype в цепочке прототипов obj?». Движок берёт прототип объекта, сравнивает, не совпало — поднимается на уровень выше, и так до null. Ответ всегда булев: true или false.
Наследование поэтому работает бесплатно: у Cat.prototype в цепочке стоит Animal.prototype, и оба ответа — true. Справа при этом стоит не сам класс, а его свойство prototype: именно этот объект ищут в цепочке.
Отсюда главное следствие: instanceof ничего не знает о том, кто на самом деле создал объект. Он смотрит только на текущую цепочку — а её можно переписать на ходу.
typeof отвечает на вопрос «какого сорта это значение» и возвращает строку. Он работает с чем угодно, включая примитивы, но различает всего восемь вариантов — и все объекты, кроме функций, сваливает в одну кучу с меткой object. Там же по историческому недоразумению оказался null, который объектом вовсе не является.
instanceof начинается там, где typeof заканчивается: он различает объекты по происхождению. Но обратная сторона — он бесполезен для примитивов. Литерал строки не объект, цепочки прототипов у него нет; объект-обёртка возникает только на время вызова метода и тут же исчезает.
| Значение | typeof | instanceof Object |
|---|---|---|
| 42 | number | false |
| «текст» | string | false |
| [1, 2] | object | true |
| null | object | false |
| function () {} | function | true |
| new Map() | object | true |
Массив — объект, и typeof честно говорит object. instanceof Array ответ даёт, но у него есть слабое место: каждое окно, iframe и воркер имеют собственный набор встроенных классов. Массив, приехавший из соседнего окна, наследует от чужого Array.prototype — и проверка внезапно возвращает false, хотя перед вами настоящий массив.
Array.isArray смотрит на внутреннюю метку значения, а не на прототипы, поэтому отвечает правильно всегда. Правило простое: массивы проверяем через Array.isArray, всё остальное — через instanceof.
Старый приём: Object.prototype.toString достаёт ту самую внутреннюю метку и отдаёт её строкой. Он тоже не зависит от цепочки прототипов и различает то, что typeof путает.
В catch прилетает всё подряд: и ожидаемая ошибка разбора данных, и опечатка в имени переменной. Разделить их — прямая работа для instanceof: встроенные классы ошибок образуют иерархию с общим предком Error.
Свой класс ошибки наследуют от Error — и вся иерархия сразу становится проверяемой. Поле name задают вручную (полем класса или в конструкторе): без него ошибка будет представляться просто как Error. Здесь важна только проверяемость иерархии; как такие классы устроены внутри — с cause, доменными полями и общим предком на всё семейство — разбирает статья «Свои классы ошибок».
В цепочке проверок идите от узкого к широкому: ветка с instanceof Error, поставленная первой, перехватит вообще всё.
instanceof — не зашитая намертво магия. Прежде чем идти по прототипам, движок ищет у правого операнда метод Symbol.hasInstance; если тот есть, оператор просто вызывает его и приводит результат к булеву. Цепочка прототипов при этом не трогается вовсе.
В обычном коде такое почти не нужно, но знать полезно по двум причинам. Во-первых, это объясняет странное поведение чужой библиотеки: проверку могли переопределить. Во-вторых, видно, что справа обязан стоять объект, который умеет отвечать на вопрос, — иначе будет не false, а исключение.
Объект с полем 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) должны попадать в общую ветку.
Ключевое — порядок проверок. NotFoundError является и AppError, и Error, поэтому широкие ветки ставим последними: иначе они перехватят всё.
Заметьте: instanceof AppError не пришлось дублировать для наследников — цепочка прототипов делает это за нас.
Свой ответ на instanceof
Доработайте объект PositiveNumber так, чтобы проверка instanceof отвечала true только для положительных чисел. Строка "7" положительным числом не считается.
Достаточно добавить метод Symbol.hasInstance — тогда объект отвечает на вопрос сам, и требование «справа должна быть функция» отпадает.
Проверка typeof value === "number" обязательна: без неё сравнение "7" > 0 дало бы true из-за приведения типов.