что такое typeof в javascript
Восемь типов данных, typeof
Материал на этой странице устарел, поэтому скрыт из оглавления сайта.
Более новая информация по этой теме находится на странице https://learn.javascript.ru/types.
В JavaScript существует несколько основных типов данных.
В этой главе мы получим о них общее представление, а позже, в соответствующих главах подробно познакомимся с использованием каждого типа в отдельности.
Число «number»
Единый тип число используется как для целых, так и для дробных чисел.
Существуют специальные числовые значения Infinity (бесконечность) и NaN (ошибка вычислений).
Например, бесконечность Infinity получается при делении на ноль:
Ошибка вычислений NaN будет результатом некорректной математической операции, например:
Эти значения формально принадлежат типу «число», хотя, конечно, числами в их обычном понимании не являются.
Особенности работы с числами в JavaScript разобраны в главе Числа.
Число «bigint»
Тип BigInt был добавлен в JavaScript, чтобы дать возможность работать с целыми числами произвольной длины.
Более подробно тип данных BigInt мы рассмотрим в отдельной главе BigInt.
Строка «string»
В JavaScript одинарные и двойные кавычки равноправны. Можно использовать или те или другие.
Более подробно со строками мы познакомимся в главе Строки.
Булевый (логический) тип «boolean»
У него всего два значения: true (истина) и false (ложь).
Как правило, такой тип используется для хранения значения типа да/нет, например:
О нём мы поговорим более подробно, когда будем обсуждать логические вычисления и условные операторы.
Специальное значение «null»
Значение null не относится ни к одному из типов выше, а образует свой отдельный тип, состоящий из единственного значения null :
В JavaScript null не является «ссылкой на несуществующий объект» или «нулевым указателем», как в некоторых других языках. Это просто специальное значение, которое имеет смысл «ничего» или «значение неизвестно».
В частности, код выше говорит о том, что возраст age неизвестен.
Специальное значение «undefined»
Если переменная объявлена, но в неё ничего не записано, то её значение как раз и есть undefined :
Можно присвоить undefined и в явном виде, хотя это делается редко:
Символы «symbol»
«Символ» представляет собой уникальный идентификатор.
Создаются новые символы с помощью функции Symbol() :
При создании символу можно дать описание (также называемое имя), в основном использующееся для отладки кода:
Символы гарантированно уникальны. Даже если мы создадим множество символов с одинаковым описанием, это всё равно будут разные символы. Описание – это просто метка, которая ни на что не влияет.
Например, вот два символа с одинаковым описанием – но они не равны:
Более подробно c символами мы познакомимся в главе Тип данных Symbol.
Объекты «object»
Первые 7 типов называют «примитивными».
Особняком стоит восьмой тип: «объекты».
Он используется для коллекций данных и для объявления более сложных сущностей.
Объявляются объекты при помощи фигурных скобок <. >, например:
Мы подробно разберём способы объявления объектов и, вообще, работу с объектами, позже, в главе Объекты.
Оператор typeof
Оператор typeof возвращает тип аргумента.
У него есть два синтаксиса: со скобками и без:
Работают они одинаково, но первый синтаксис короче.
Результатом typeof является строка, содержащая тип:
Последние две строки помечены, потому что typeof ведёт себя в них по-особому.
К работе с типами мы также вернёмся более подробно в будущем, после изучения основных структур данных.
Итого
Очень скоро мы изучим их во всех деталях.
Типы данных: [[Class]], instanceof и утки
Материал на этой странице устарел, поэтому скрыт из оглавления сайта.
Более новая информация по этой теме находится на странице https://learn.javascript.ru/instanceof.
Время от времени бывает удобно создавать так называемые «полиморфные» функции, то есть такие, которые по-разному обрабатывают аргументы, в зависимости от их типа. Например, функция вывода может по-разному форматировать числа и даты.
Для реализации такой возможности нужен способ определить тип переменной.
Оператор typeof
Мы уже знакомы с простейшим способом – оператором typeof.
…Но все объекты, включая массивы и даты для typeof – на одно лицо, они имеют один тип ‘object’ :
Поэтому различить их при помощи typeof нельзя, и в этом его основной недостаток.
Секретное свойство [[Class]]
Метод также можно использовать с примитивами:
При тестировании кода в консоли вы можете обнаружить, что если ввести в командную строку <>.toString.call(. ) – будет ошибка. С другой стороны, вызов alert( <>.toString. ) – работает.
Эта ошибка возникает потому, что фигурные скобки < >в основном потоке кода интерпретируются как блок. Интерпретатор читает <>.toString.call(. ) так:
Фигурные скобки считаются объектом, только если они находятся в контексте выражения. В частности, оборачивание в скобки ( <>.toString. ) тоже сработает нормально.
Поэтому узнать тип таким образом можно только для встроенных объектов.
Метод Array.isArray()
Но этот метод – единственный в своём роде.
Оператор instanceof
Оператор instanceof позволяет проверить, создан ли объект данной функцией, причём работает для любых функций – как встроенных, так и наших.
Заметим, что оператор instanceof – сложнее, чем кажется. Он учитывает наследование, которое мы пока не проходили, но скоро изучим и затем вернёмся к instanceof в главе Проверка класса: «instanceof».
Утиная типизация
Альтернативный подход к типу – «утиная типизация», которая основана на одной известной пословице: «If it looks like a duck, swims like a duck and quacks like a duck, then it probably is a duck (who cares what it really is)».
В переводе: «Если это выглядит как утка, плавает как утка и крякает как утка, то, вероятно, это утка (какая разница, что это на самом деле)».
Смысл утиной типизации – в проверке необходимых методов и свойств.
Проверить на дату можно, определив наличие метода getTime :
С виду такая проверка хрупка, её можно «сломать», передав похожий объект с тем же методом.
Но как раз в этом и есть смысл утиной типизации: если объект похож на дату, у него есть методы даты, то будем работать с ним как с датой (какая разница, что это на самом деле).
То есть мы намеренно позволяем передать в код нечто менее конкретное, чем определённый тип, чтобы сделать его более универсальным.
Если говорить словами «классического программирования», то «duck typing» – это проверка реализации объектом требуемого интерфейса. Если реализует – ок, используем его. Если нет – значит это что-то другое.
Пример полиморфной функции
Проверку на массив в этом примере можно заменить на «утиную» – нам ведь нужен только метод forEach :
Итого
Для написания полиморфных (это удобно!) функций нам нужна проверка типов.
У него две особенности:
И, наконец, зачастую достаточно проверить не сам тип, а просто наличие нужных свойств или методов. Это называется «утиная типизация».
Задачи
Полиморфная функция formatDate
Её первый аргумент должен содержать дату в одном из видов:
Для этого вам понадобится определить тип данных аргумента и, при необходимости, преобразовать входные данные в нужный формат.
Для определения примитивного типа строка/число подойдёт оператор typeof.
Примеры его работы:
typeof Everything и утиные недоразумения
Каждый, использующий в каких бы то ни было целях замечательный JavaScript, задавался вопросом: мол а почему typeof null — это «object»? typeof от функции возвращает «function», но от Array — «object»? а где же getClass у ваших хваленых классов? И хотя на большую часть легко и непринужденно отвечает спецификация или исторические факты, я бы хотел немного подвести черту… в большей степени для самого себя.
Если, читатель, тебе в твоих задачах тоже недостаточно typeof да instanceof и хочется какой-то конкретики, а не «object»ы, то дальше может быть полезно. Ах да, про утки: они будут тоже, только немного неправильные.
Краткая история вопроса
Достоверно получить тип какой-то переменной в JavaScript всегда было задачей нетривиальной, точно не для новичка. В большинстве случаев оно конечно и не требуется, просто:
и вот вы уже не ловите Cannot read property of null — местный аналог NPE. Знакомо?
А потом мы начали все чаще использовать функции как конструкторы, а проинспектировать тип созданного таким образом объекта иногда полезно. Но просто использовать typeof от экземпляра не получится, так как мы верно получим «object».
Тогда еще было нормальным использовать прототипную модель ООП в JavaScript, помните? У нас есть некоторый объект, и через ссылку на его прототип мы можем найти свойство constructor, указывающее на функцию, с помощью которой объект был создан. А дальше немного магии с toString от функции и регулярными выражениями и вот он результат:
Иногда такое на собеседованиях спрашивали, но зачем?
Да мы просто могли в прототип сохранять специальным свойством строковое представление типа и от объекта получать его по цепочке прототипов:
Только два раза приходится писать «Type»: в объявлении функции и в свойство.
Для встроенных же объектов (как Array или Date) у нас было секретное свойство [[Class]], которое можно было выцепить через toString от стандартного объекта Object:
Сейчас у нас появились классы, и пользовательские типы окончательно закрепились в языке: это вам уже не какой-нибудь LiveScript; мы пишем поддерживаемый код в больших количествах!
Примерно в это же время появились Symbol.toStringTag и Function.name, с помощью которых мы можем по-новому взять наш typeof.
Вообще, прежде чем мы перейдем далее, хочу отметить, что рассматриваемый вопрос эволюционирует на StackOverflow вместе с языком и поднимается от редакции к редакции: 9 лет назад, 7 лет назад, не так давно или это и это еще.
Текущее положение дел
Ранее мы уже рассматривали достаточно подробно Symbol.toStringTag и Function.name. Если коротко, то внутренний символ toStringTag — это современный [[Class]], только мы можем его переопределять для своих объектов. А свойство Function.name — это узаконенный почти во всех браузерах тот самый typeName из примера: возвращает название функции.
Не долго думая, можно определить такую функцию:
Объект с toStringTag:
Класс с toStringTag:
→ Больше примеров можно посмотреть в этом репозитории
Таким образом, сейчас довольно просто можно определить тип любой переменной в JavaScript. Эта функция позволяет единообразно проверять переменные на тип и использовать простой switch expression вместо утиных проверок в полиморфных функциях. Мне вообще никогда не нравился подход, основанный на утиной типизации мол если у чего-то есть свойство splice, то это массив что ли?
Какие-то неправильные утки
Понимать тип переменной по наличию определенных методов или свойств — дело конечно каждого и зависит от ситуации. Но я возьму этот getTag и поинспектирую некоторые штуки языка.
Классы?
Моя любимая «утка» в JavaScript — это классы. Ребята, которые начинали писать на JavaScript c ES-2015, бывает и не подозревают, что эти классы из себя представляют. И правда:
У нас есть ключевое слово class, констуктор, какие-то методы, даже extends. Еще мы создаем экземпляр этого класса через new. Выглядит как класс в привычном его понимании — значит класс!
Однако, когда начинаешь добавлять новые методы в real-time в «класс», и при этом они становятся сразу доступными для уже созданных экземляров, некоторые теряются:
Не надо так делать!
А не некоторые достоверно знают, что это всего лишь синтаксический сахар над прототипной моделью, потому что концептуально в языке ничего не поменялось. Если вызвать getTag от Person, то получим «Function», а не выдуманный «Class», и об этом стоит помнить.
Другие функции
В JavaScript есть несколько способов объявления функции: FunctionDeclaration, FunctionExpression и недавно ArrowFunction. Все мы знаем, когда и что следует использовать: вещи то довольно разные. При этом если вызвать getTag от функции, объявленной любым из предложенных вариантов, то получим «Function».
На самом деле, в языке гораздо больше способов задать функцию. Добавим в список как минимум рассмотренный ClassDeclaration, потом асинхронные функции и генераторы, прочее: AsyncFunctionDeclaration, AsyncFunctionExpression, AsyncArrowFunction, GeneratorDeclaration, GeneratorExpression, ClassExpression, MethodDefinition (список не полный). И кажется мол ну и что? все перечисленное ведет себя как функция — значит тоже getTag вернет «Function». Но есть одна особенность: все варианты безусловно являются функциями, но не все напрямую — Function.
The Function constructor is designed to be subclassable.
There is no syntactic means to create instances of Function subclasses except for the built-in GeneratorFunction and AsyncFunction subclasses.
У нас есть Function и внутри «унаследованные» от него GeneratorFunction и AsyncFunction со своими конструкторами. Это подчеркивает, что асинки и генераторы имеют свою уникальную природу. И как итог:
При этом мы не можем инстанцировать такую функцию через оператор new, а ее вызов возвращает нам Promise:
Пример с функцией-генератором:
Вызов такой функции возвращает нам экземпляр — объект Generator:
Символ toStringTag справедливо переопределен для асинков и генераторов. А вот typeof для любой функции покажет «function».
Встроенные объекты
У нас есть такие вещи, как например Set, Map, Date или Error. Применение getTag к ним вернет «Function», потому что это и есть функции — конструкторы итерируемых коллекций, даты и ошибки. От экземпляров мы получим соответственно — «Set», «Map», «Date» и «Error«.
Но осторожно! еще есть такие объекты, как JSON или Math. Если поторопиться, то можно предположить аналогичную ситуацию. Но нет! это совсем другое — встроенные объекты-одиночки. Они не инстанцируемы ( is not a constructor ). Вызов typeof вернет «object» (кто бы сомневался). А вот getTag обратится к toStringTag и получит «JSON» и «Math». Это последнее наблюдение, которым мне хочется поделиться.
Разбираемся в проверке JavaScript-типов с помощью typeof
Очень важный аспект любого языка программирования — это его система типов и типы данных в нем. Для строго типизированных языков программирования, например для таких как Java, переменные определяются конкретными типами, которые в свою очередь ограничивают значения переменных.
Несмотря на то, что JavaScript — это динамически типизированный язык программирования, существуют расширения над языком, которые поддерживают строгую типизацию, например TypeScript.
Для проверки типов в JavaScript присутствует довольно простой оператор typeof
Однако позже мы с вами увидим, что использование этого оператора может ввести в заблуждение, об этом мы поговорим ниже.
Типы данных в JavaScript
Перед тем как начать рассматривать проверку типов с помощью оператора typeof важно взглянуть на существующие типы данных в JavaScript. Хотя в этой статье не рассматриваются подробные сведения о типах данных JavaScript, вы все равно сможете что-то почерпнуть по мере чтения статьи.
Первые шесть типов данных относятся к примитивным типам. Все другие типы данных помимо вышеуказанных шести являются объектами и относятся к ссылочному типу. Объект — это не что иное, как коллекция свойств, представленная в виде пар ключ и значение.
Обратите внимание, что в указанном списке типов данных, null и undefined — это примитивные типы в JavaScript, которые содержат ровно одно значение.
Вы уже наверно начали задаваться вопросом, а как же массивы, функции, регулярные выражения и прочие вещи? Все это специальные виды объектов.
JavaScript содержит несколько конструкторов для создания и других различных объектов, например, таких как:
Проверка типов с использованием typeof
Синтаксис
Оператор typeof в JavaScript является унарным оператором (принимает только один операнд), который возвращает строковое значение типа операнда. Как и другие унарные операторы, он помещается перед его операндом, разделенный пробелом:
Однако существует альтернативный синтаксис, который позволяет использовать typeof похожим на вызов функции, через оборачивание операнда в круглые скобки. Это очень полезно при проверке типов возвращаемого значения из JavaScript-выражения:
Защита от ошибок
До спецификации ES6 оператор typeof всегда возвращал строку независимо от операнда, который использовал.
Для необъявленных идентификаторов функция typeof вернет “undefined” вместо того, чтобы выбросить исключение ReferenceError.
Переменные, имеющие блочную область видимости остаются во временной мертвой зоне до момента инициализации:
Проверка типов
Следующий фрагмент кода демонстрирует проверку типов c использованием оператора typeof :
Ниже представлена сводка результатов проверок типов:
Улучшенная проверка типов
Дополнительные проверки могут быть сделаны при использовании некоторых других характеристик, например:
Проверка на null
Очень важным здесь является использование оператора строгого сравнения. Следующий фрагмент кода иллюстрирует использования значения undefined :
Проверка на NaN
Любая арифметическая операция, включающая в выражение NaN, всегда определяется как NaN.
Если вы действительно хотите применить произвольное значения для любой арифметической операции, тогда убедитесь, что это значение не NaN.
Значение NaN в JavaScript имеет отличительную особенность. Это единственное значение в JavaScript, которое при сравнении с каким-либо другим значением, включая NaN, не будет ему эквивалентно
Вы можете проверить на значение NaN следующим образом
Проверка для массивов
Очень важным здесь является использование оператора строгого сравнения. Следующий фрагмент кода иллюстрирует использования значения undefined :
Общий подход к проверке типов
Как вы видели на примере массивов, метод Object.prototype.toString() может быть полезным при проверки типов объектов для любого значения в JavaScript.
Рассмотрим следующий фрагмент кода:
Фрагмент кода ниже демонстрирует результаты проверки типов с использованием созданной функции type() :
Бонус: не все является объектами
Очень вероятно, что в какой-то момент вы могли столкнуться с этим утверждением:
“Все сущности в JavaScript являются объектами.” — Неправда
Это ошибочное утверждение и на самом деле это неправда. Не все в JavaScript является объектами. Примитивы не являются объектами.
Вы можете начать задаваться вопросом — почему же мы можем делать следующие операции над примитивами, если они не являются объектами?
Причина, по которой мы можем достичь всего этого над примитивами, заключается в том, что JavaScript-движок неявно создает соответствующий объект-обертку для примитива и затем вызывает указанный метод или обращается к указанному свойству.
Когда значение было возвращено, объект-обертка отбрасывается и удаляется из памяти. Для операций, перечисленных ранее, JavaScript-движок неявно выполняет следующие действия:
Заключение
Также вы видели, как проверка типов с помощью оператора typeof может ввести в заблуждение. И, наконец, вы видели несколько способов реализации предсказуемой проверки типов для некоторых типов данных.
Если вы заинтересованы в получении дополнительной информации об операторе typeof в JavaScript, обратитесь к этой статье.
Типы данных
Значение в JavaScript всегда относится к данным определённого типа. Например, это может быть строка или число.
Есть восемь основных типов данных в JavaScript. В этой главе мы рассмотрим их в общем, а в следующих главах поговорим подробнее о каждом.
Переменная в JavaScript может содержать любые данные. В один момент там может быть строка, а в другой – число:
Языки программирования, в которых такое возможно, называются «динамически типизированными». Это значит, что типы данных есть, но переменные не привязаны ни к одному из них.
Число
Числовой тип данных ( number ) представляет как целочисленные значения, так и числа с плавающей точкой.
Infinity представляет собой математическую бесконечность ∞. Это особое значение, которое больше любого числа.
Мы можем получить его в результате деления на ноль:
Или задать его явно:
NaN означает вычислительную ошибку. Это результат неправильной или неопределённой математической операции, например:
Значение NaN «прилипчиво». Любая операция с NaN возвращает NaN :
Математические операции в JavaScript «безопасны». Мы можем делать что угодно: делить на ноль, обращаться с нечисловыми строками как с числами и т.д.
Скрипт никогда не остановится с фатальной ошибкой (не «умрёт»). В худшем случае мы получим NaN как результат выполнения.
Специальные числовые значения относятся к типу «число». Конечно, это не числа в привычном значении этого слова.
Подробнее о работе с числами мы поговорим в главе Числа.
BigInt
Для большинства случаев этого достаточно. Но иногда нам нужны действительно гигантские числа, например, в криптографии или при использовании метки времени («timestamp») с микросекундами.
Тип BigInt был добавлен в JavaScript, чтобы дать возможность работать с целыми числами произвольной длины.
В данный момент BigInt поддерживается только в браузерах Firefox, Chrome, Edge и Safari, но не поддерживается в IE.
Строка
Строка ( string ) в JavaScript должна быть заключена в кавычки.
В JavaScript существует три типа кавычек.
Двойные или одинарные кавычки являются «простыми», между ними нет разницы в JavaScript.
Обратите внимание, что это можно делать только в обратных кавычках. Другие кавычки не имеют такой функциональности встраивания!
Мы рассмотрим строки более подробно в главе Строки.
Булевый (логический) тип
Булевый тип ( boolean ) может принимать только два значения: true (истина) и false (ложь).
Такой тип, как правило, используется для хранения значений да/нет: true значит «да, правильно», а false значит «нет, не правильно».
Булевые значения также могут быть результатом сравнений:
Мы рассмотрим булевые значения более подробно в главе Логические операторы.
Значение «null»
Специальное значение null не относится ни к одному из типов, описанных выше.
Оно формирует отдельный тип, который содержит только значение null :
В JavaScript null не является «ссылкой на несуществующий объект» или «нулевым указателем», как в некоторых других языках.
Это просто специальное значение, которое представляет собой «ничего», «пусто» или «значение неизвестно».
В приведённом выше коде указано, что значение переменной age неизвестно.
Значение «undefined»
Оно означает, что «значение не было присвоено».
Если переменная объявлена, но ей не присвоено никакого значения, то её значением будет undefined :
Технически мы можем присвоить значение undefined любой переменной:
…Но так делать не рекомендуется. Обычно null используется для присвоения переменной «пустого» или «неизвестного» значения, а undefined – для проверок, была ли переменная назначена.
Объекты и символы
Тип object (объект) – особенный.
Все остальные типы называются «примитивными», потому что их значениями могут быть только простые значения (будь то строка, или число, или что-то ещё). В объектах же хранят коллекции данных или более сложные структуры.
Объекты занимают важное место в языке и требуют особого внимания. Мы разберёмся с ними в главе Объекты после того, как узнаем больше о примитивах.
Тип symbol (символ) используется для создания уникальных идентификаторов в объектах. Мы упоминаем здесь о нём для полноты картины, изучим этот тип после объектов.
Оператор typeof
Оператор typeof возвращает тип аргумента. Это полезно, когда мы хотим обрабатывать значения различных типов по-разному или просто хотим сделать проверку.
У него есть две синтаксические формы:
Другими словами, он работает со скобками или без скобок. Результат одинаковый.
Вызов typeof x возвращает строку с именем типа:
Последние три строки нуждаются в пояснении:
Итого
В JavaScript есть 8 основных типов.
Оператор typeof позволяет нам увидеть, какой тип данных сохранён в переменной.
В следующих главах мы сконцентрируемся на примитивных значениях, а когда познакомимся с ними, перейдём к объектам.


