Оператор instanceof
позволяет проверить принадлежность объекта к определенному классу. Также учитывается наследование.
Такая проверка может потребоваться во многих случаях. Например, его можно использовать для создания полиморфной функции, которая обрабатывает аргументы по-разному в зависимости от их типа.
Синтаксис:
объектный экземпляр класса
Он возвращает true
если obj
принадлежит Class
или классу, наследующему от него.
Например:
класс Кролик {} пусть кролик = новый кролик (); // это объект класса Rabbit? предупреждение (экземпляр кролика Rabbit); // истинный
Он также работает с функциями конструктора:
// вместо класса функция Кролик() {} Предупреждение (новый Rabbit() экземпляр Rabbit); // истинный
…И со встроенными классами, такими как Array
:
пусть arr = [1, 2, 3]; Предупреждение (arr экземпляра массива); // истинный предупреждение (arr экземпляра объекта); // истинный
Обратите внимание, что arr
также принадлежит классу Object
. Это потому, что Array
прототипически наследуется от Object
.
Обычно instanceof
проверяет цепочку прототипов. Мы также можем установить собственную логику в статическом методе Symbol.hasInstance
.
Алгоритм obj instanceof Class
работает примерно следующим образом:
Если есть статический метод Symbol.hasInstance
, просто вызовите его: Class[Symbol.hasInstance](obj)
. Он должен вернуть либо true
, либо false
, и все готово. Вот как мы можем настроить поведение instanceof
.
Например:
// настройка проверки экземпляра, которая предполагает, что // все, что имеет свойство canEat, является животным класс Животное { статический [Symbol.hasInstance](obj) { if (obj.canEat) возвращает true; } } пусть obj = {canEat: true}; предупреждение (экземпляр объекта Animal); // true: вызывается Animal[Symbol.hasInstance](obj)
Большинство классов не имеют Symbol.hasInstance
. В этом случае используется стандартная логика: obj instanceOf Class
проверяет, равен ли Class.prototype
одному из прототипов в цепочке прототипов obj
.
Другими словами, сравните одно за другим:
obj.__proto__ === Class.prototype? obj.__proto__.__proto__ === Class.prototype? obj.__proto__.__proto__.__proto__ === Class.prototype? ... // если какой-либо ответ верен, возвращаем true // иначе, если мы дошли до конца цепочки, возвращаем false
В приведенном выше примере rabbit.__proto__ === Rabbit.prototype
, так что ответ дается немедленно.
В случае наследования совпадение будет на втором этапе:
класс Животное {} класс Rabbit расширяет Animal {} пусть кролик = новый кролик (); alert(экземпляр кролика Animal); // истинный // 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
будет изменено после создания объекта.
Как здесь:
функция Кролик() {} пусть кролик = новый кролик (); // изменил прототип Кролик.прототип = {}; // ...больше не кролик! предупреждение (экземпляр кролика Rabbit); // ЛОЖЬ
Мы уже знаем, что простые объекты преобразуются в строку как [object Object]
:
пусть объект = {}; предупреждение (объект); // [объект Объект] оповещение(obj.toString()); // одинаковый
Это их реализация toString
. Но есть скрытая функция, которая делает toString
гораздо более мощным инструментом. Мы можем использовать его как расширенный typeof
и как альтернативу instanceof
.
Звучит странно? Действительно. Давайте демистифицируем.
По спецификации встроенный toString
может быть извлечен из объекта и выполнен в контексте любого другого значения. И его результат зависит от этого значения.
Для числа это будет [object Number]
Для логического значения это будет [object Boolean]
Для null
: [object Null]
Для undefined
: [object Undefined]
Для массивов: [object Array]
… и т. д. (настраиваемый).
Давайте продемонстрируем:
// копируем метод toString в переменную для удобства пусть objectToString = Object.prototype.toString; // какой это тип? пусть arr = []; Предупреждение( objectToString.call(arr) ); // [массив объектов]
Здесь мы использовали вызов, как описано в главе «Декораторы и пересылка», вызов/применение для выполнения функции objectToString
в контексте this=arr
.
Внутренне алгоритм toString
проверяет this
и возвращает соответствующий результат. Еще примеры:
пусть s = Object.prototype.toString; предупреждение(s.call(123)); // [номер объекта] оповещение(s.call(null)); // [объект Null] оповещение(s.call(оповещение)); // [Функция объекта]
Поведение Object toString
можно настроить с помощью специального свойства объекта Symbol.toStringTag
.
Например:
пусть пользователь = { [Symbol.toStringTag]: «Пользователь» }; предупреждение( {}.toString.call(пользователь)); // [объект Пользователь]
Такое свойство имеется у большинства объектов, зависящих от среды. Вот несколько примеров для конкретных браузеров:
// toStringTag для объекта и класса, зависящего от среды: предупреждение(окно[Symbol.toStringTag]); // Окно оповещение( XMLHttpRequest.prototype[Symbol.toStringTag]); // XMLHttpRequest alert( {}.toString.call(окно) ); // [Окно объекта] alert( {}.toString.call(новый XMLHttpRequest()) ); // [объект XMLHttpRequest]
Как видите, результатом является именно Symbol.toStringTag
(если существует), завернутый в [object ...]
.
В конце у нас есть «typeof на стероидах», который работает не только с примитивными типами данных, но и со встроенными объектами и даже может быть настроен.
Мы можем использовать {}.toString.call
вместо instanceof
для встроенных объектов, когда хотим получить тип в виде строки, а не просто для проверки.
Подытожим известные нам методы проверки типов:
работает на | возвращает | |
---|---|---|
typeof | примитивы | нить |
{}.toString | примитивы, встроенные объекты, объекты с Symbol.toStringTag | нить |
instanceof | объекты | правда/ложь |
Как мы видим, {}.toString
технически является «более продвинутым» typeof
.
И оператор instanceof
действительно полезен, когда мы работаем с иерархией классов и хотим проверить класс с учетом наследования.
важность: 5
Почему в приведенном ниже коде instanceof
возвращает true
? Мы легко можем видеть, что a
не создается с помощью B()
.
функция А() {} функция Б() {} А.прототип = Б.прототип = {}; пусть a = новый A(); предупреждение (экземпляр B); // истинный
Да, действительно выглядит странно.
Но instanceof
заботится не о функции, а о ее prototype
, который он сопоставляет с цепочкой прототипов.
А здесь a.__proto__ == B.prototype
, поэтому instanceof
возвращает true
.
Итак, по логике instanceof
, prototype
фактически определяет тип, а не функцию-конструктор.