Оператор instanceof позволяет проверить, принадлежит ли объект указанному классу, с учётом наследования.
Такая проверка может потребоваться во многих случаях. Здесь мы используем её для создания полиморфной функции, которая интерпретирует аргументы по-разному в зависимости от их типа.
Оператор instanceof
Синтаксис:
Оператор вернёт true, если obj принадлежит классу Class или наследующему от него.
Например:
Также это работает с функциями-конструкторами:
…И для встроенных классов, таких как Array:
Пожалуйста, обратите внимание, что arr также принадлежит классу Object, потому что Array наследует от Object.
Обычно оператор instanceof просматривает для проверки цепочку прототипов. Но это поведение может быть изменено при помощи статического метода Symbol.hasInstance.
Алгоритм работы obj instanceof Class работает примерно так:
-
Если имеется статический метод Symbol.hasInstance, тогда вызвать его: Class[Symbol.hasInstance](obj). Он должен вернуть либо true, либо false, и это конец. Это как раз и есть возможность ручной настройки instanceof.
Пример:
// проверка instanceof будет полагать, // что всё со свойством canEat - животное Animal class Animal { static [Symbol.hasInstance](obj) { if (obj.canEat) return true; } } let obj = { canEat: true }; alert(obj instanceof Animal); // true: вызван Animal[Symbol.hasInstance](obj) -
Большая часть классов не имеет метода Symbol.hasInstance. В этом случае используется стандартная логика: проверяется, равен ли Class.prototype одному из прототипов в прототипной цепочке obj.
Другими словами, сравнивается:
obj.__proto__ === Class.prototype? obj.__proto__.__proto__ === Class.prototype? obj.__proto__.__proto__.__proto__ === Class.prototype? ... // если какой-то из ответов true - возвратить true // если дошли до конца цепочки - falseВ примере выше rabbit.__proto__ === Rabbit.prototype, так что результат будет получен немедленно.
В случае с наследованием, совпадение будет на втором шаге:
class Animal {} class Rabbit extends Animal {} let rabbit = new Rabbit(); alert(rabbit instanceof Animal); // true // rabbit.__proto__ === Animal.prototype (нет совпадения) // rabbit.__proto__.__proto__ === Animal.prototype (совпадение!)
Вот иллюстрация того как rabbit instanceof Animal сравнивается с Animal.prototype:
Кстати, есть метод objA.isPrototypeOf(objB), который возвращает true, если объект objA есть где-то в прототипной цепочке объекта objB. Так что obj instanceof Class можно перефразировать как Class.prototype.isPrototypeOf(obj).
Забавно, но сам конструктор Class не участвует в процессе проверки! Важна только цепочка прототипов Class.prototype.
Это может приводить к интересным последствиям при изменении свойства prototype после создания объекта.
Как, например, тут:
Бонус: Object.prototype.toString возвращает тип
Мы уже знаем, что обычные объекты преобразуются к строке как [object Object]:
Так работает реализация метода toString. Но у toString имеются скрытые возможности, которые делают метод гораздо более мощным. Мы можем использовать его как расширенную версию typeof и как альтернативу instanceof.
Звучит странно? Так и есть. Давайте развеем мистику.
Согласно спецификации встроенный метод toString может быть позаимствован у объекта и вызван в контексте любого другого значения. И результат зависит от типа этого значения.
- Для числа это будет [object Number]
- Для булева типа это будет [object Boolean]
- Для null: [object Null]
- Для undefined: [object Undefined]
- Для массивов: [object Array]
- …и т.д. (поведение настраивается).
Давайте продемонстрируем:
В примере мы использовали call, как описано в главе Декораторы и переадресация вызова, call/apply, чтобы выполнить функцию objectToString в контексте this=arr.
Внутри, алгоритм метода toString анализирует контекст вызова this и возвращает соответствующий результат. Больше примеров:
Symbol.toStringTag
Поведение метода объектов toString можно настраивать, используя специальное свойство объекта Symbol.toStringTag.
Например:
Такое свойство есть у большей части объектов, специфичных для определённых окружений. Вот несколько примеров для браузера:
Как вы можете видеть, результат – это значение Symbol.toStringTag (если он имеется) обёрнутое в [object ...].
В итоге мы получили «typeof на стероидах», который не только работает с примитивными типами данных, но также и со встроенными объектами, и даже может быть настроен.
Можно использовать {}.toString.call вместо instanceof для встроенных объектов, когда мы хотим получить тип в виде строки, а не просто сделать проверку.
Итого
Давайте обобщим, какие методы для проверки типа мы знаем:
| typeof | примитивов | строка |
| {}.toString | примитивов, встроенных объектов, объектов с Symbol.toStringTag | строка |
| instanceof | объектов | true/false |
Как мы можем видеть, технически {}.toString «более продвинут», чем typeof.
А оператор instanceof – отличный выбор, когда мы работаем с иерархией классов и хотим делать проверки с учётом наследования.
Комментарии