Типы данных в JS на первый взгляд вроде бы простая штука, пока не начнёшь в неё погружаться. Здесь и сюрпризы с NaN, и странности с преобразованиями, и многое другое.
В этой статье разберёмся: какие типы есть в JavaScript, как они сравниваются, как правильно использовать оператор typeof и где вас точно подстерегают баги, если не быть внимательным.
Введение в типы данных
Если вы уже писали хоть пару строк на JavaScript, с типами данных уже знакомы. Числа, строки, булевы значения, объекты, массивы и так далее. Мы не будем расписывать каждый тип по пунктам, но ключевые моменты всё же напомним.
Типы делятся на примитивные и ссылочные.
Примитивы — это string, number, boolean, bigint, symbol, undefined
и null
. Они хранятся прямо в переменной, не имеют методов (но иногда ведут себя так, будто имеют) и при передаче в функцию копируются.
Ссылочные — это всё, что объект: Object, Array, Function, Date, RegExp
и т. д. Они передаются по ссылке: передали массив в функцию, поменяли его внутри — снаружи он тоже поменялся.
Важно помнить, что JavaScript — язык с динамической и нестрогой типизацией. То есть значение может быть числом, а потом строкой, а потом — NaN.
Полезный блок со скидкой
Если вам интересно разбираться со смартфонами, компьютерами и прочими гаджетами и вы хотите научиться создавать софт под них с нуля или тестировать то, что сделали другие, — держите промокод Практикума на любой платный курс: KOD (можно просто на него нажать). Он даст скидку при покупке и позволит сэкономить на обучении.
Бесплатные курсы в Практикуме тоже есть — по всем специальностям и направлениям, начать можно в любой момент, карту привязывать не нужно, если что.
Оператор typeof
В JavaScript типы данных могут вести себя непредсказуемо. Значение, которое было числом, в следующей строке уже становится строкой, а null
внезапно оказывается объектом. Чтобы понять, с чем вы имеете дело прямо сейчас, используют оператор typeof
. Это встроенный механизм, который возвращает строку с названием типа значения.
Он особенно полезен при работе с вводом пользователя, API, валидацией данных, а также в ситуациях, когда нужно убедиться, что переменная содержит именно то, что вы ожидаете.
Но typeof
не всегда даёт интуитивные результаты — и в этом его особенность.
Синтаксис и использование
Оператор typeof
— унарный. Это значит, он применяется к одному значению и возвращает строку с его типом. Использовать его можно как со скобками, так и без:
typeof значение
typeof(значение)
Скобки необязательны, но иногда они помогают читаемости, особенно в сложных выражениях.
typeof 123 // 'number'
typeof 'JavaScript' // 'string'
typeof true // 'boolean'
typeof undefined // 'undefined'
Ещё можно использовать typeof
в условиях, чтобы, например, проверить, пришло ли в функцию число:
function processValue(value) {
if (typeof value === 'number') {
// работаем с числом
}
}
Удобно, когда заранее неизвестно, какой тип вы получите, особенно в динамических системах — при обработке форм, сетевых данных, пользовательского ввода.
Но typeof
не проверяет содержимое или структуру, только тип значения. И работает он неидеально. В следующем разделе посмотрим, какие именно типы он может возвращать и где начинаются нюансы.
Типы данных, возвращаемые оператором
typeof всегда возвращает строку, обозначающую тип переданного значения. Их может быть восемь видов:
- Число:
‘number’
- Строка:
‘string’
- Булево значение:
‘boolean’
- Неопределённо:
‘undefined’
- Уникальный идентификатор:
‘symbol’
- Больше число:
‘bigint’
- Функция:
‘function’
- Объект:
‘object’
Отдельно стоит упомянуть null
. Логика подсказывает, что это должен быть отдельный тип ‘null’
, но:
typeof null // ‘object’
Это историческая ошибка, которая была допущена ещё в ранних версиях JavaScript и теперь уже не подлежит исправлению — слишком много кода в интернете на неё полагается. Просто запоминаем: typeof null
возвращает ‘object’
, хотя null
— это примитив.
А ещё массивы, даты, регулярные выражения и другие встроенные объекты — это тоже ‘object’
:
typeof [1, 2, 3] // 'object'
typeof new Date() // 'object'
typeof /abc/ // 'object'
typeof
не делает различий между типами объектов, а просто говорит: «Это объект». Если нужно больше конкретики — например, понять, массив это или нет, — придётся использовать другие методы (например, Array.isArray()
). Об этом мы поговорим позже.
Типы данных: примитивные и ссылочные
Мы уже говорили, что в JavaScript все типы данных делятся на две большие категории: примитивные и ссылочные.
Примитивы — это «простые» значения. Они хранятся непосредственно в переменной и передаются по значению. Это значит, что при копировании создаётся новая копия значения, не связанная с оригиналом:
let a = 5;
let b = a;
b = 10;
console.log(a); // 5 — оригинал не изменился
Ссылочные типы — это объекты. Такие значения не копируются, а передаются по ссылке. То есть, если мы присвоим один объект другому, оба будут указывать на одну и ту же область памяти:
let obj1 = { name: 'Alice' };
let obj2 = obj1;
obj2.name = 'Bob';
console.log(obj1.name); // 'Bob' — оба объекта ссылаются на одно и то же
Это база всей сложности с мутацией данных, особенно в сложных структурах и при работе с состоянием в React, Vue и других фреймворках.
Оператор typeof
отлично справляется с примитивами: возвращает ‘string’
, ‘number’
, ‘boolean’
и т. д. Но со ссылочными типами всё чуть сложнее. Там typeof
возвращает ‘object’
практически для всего, что объект, и ‘function’
— для функций.
Преобразование типов
JavaScript слишком любит помогать разработчику, иногда даже тогда, когда его об этом не просили. Это проявляется в автоматическом приведении типов: строка может стать числом, undefined
превратиться в NaN
, а массив внезапно окажется true
в условии.
Неявное преобразование
Неявное преобразование типов происходит тогда, когда JavaScript сам решает: «А давай я тут превращу строку в число, чтобы сравнение сработало». Иногда удобно, а иногда больше мешает, чем помогает.
'5' + 3 // '53' — строка, потому что + работает как конкатенация
'5' - 3 // 2 — минус приводит строку к числу
true + 1 // 2 — true становится 1
false == 0 // true — приведение при нестрогом сравнении
null == undefined // true — специальное правило языка
[] == false // true — и вот тут уже начинается магия
JS приводит значения по разным схемам в зависимости от контекста:
- При арифметических операциях (-, *, /) всё старается превратиться в числа.
- При сложении с +, если хотя бы один операнд — строка, происходит конкатенация.
- При сравнении с == — используется алгоритм абстрактного сравнения, где движок делает всё, чтобы сравнить хоть как-то.
Вроде логично, но чем больше таких «автоматических подарков» от движка, тем сложнее отлавливать ошибки. Особенно в ветках условий, где сравниваются значения разного типа.
Явное преобразование
В отличие от неявного, явное преобразование происходит тогда, когда вы сами прямо говорите JavaScript, во что именно нужно превратить значение. Это более контролируемый и безопасный способ работать с типами.
Привести значение к строке можно через String()
:
String(123) // '123'
String(true) // 'true'
123 + '' // '123' — работает, но это уже не совсем явное
Иногда используют шаблонные строки:
`${value}` // превращает всё во что угодно в строку
Для чисел используют Number()
— он постарается превратить значение в число и, если не сможет, вернёт NaN
.
Number('42') // 42
Number('') // 0
Number('abc') // NaN
А ещё есть parseInt()
и parseFloat()
— они разбирают строку до первого нецифрового символа и могут работать с числами в формате ‘123px’
.
parseInt('123px') // 123
parseFloat('1.5em') // 1.5
Number(‘123px’)
даст NaN, а parseInt()
— 123. Так что выбирайте под задачу.
Для булевых значений используют Boolean():
Boolean(0) // false
Boolean('hello') // true
Boolean('') // false
Явное преобразование используют, когда:
- хотят точно контролировать результат;
- данные приходят из внешнего источника (например, из формы или из API);
- важно поведение:
Number(”)
даст 0, аparseInt(”)
— NaN.
Если есть сомнения, что лежит в переменной, сначала проверяйте через typeof
, потом явно приводите тип.
Сравнение типов данных
В JavaScript значения можно сравнивать двумя способами: строго (===) и нестрого (==). Различие в одном знаке, но == включает в себя автоматическое приведение типов, а === нет.
Строгое и нестрогое равенство
Строгое равенство (===) сравнивает и значение, и тип:
42 === 42 // true
'42' === 42 // false
true === 1 // false
null === undefined // false
Здесь всё логично, понятно и ожидаемо.
А вот нестрогое равенство (==) сначала приводит значения к одному типу, а уже потом сравнивает:
'42' == 42 // true — строка преобразована в число
false == 0 // true
Вот случай, когда == возвращает true
между значениями разных типов без приведения к числу. null
и undefined
равны только между собой:
null == undefined // true
Дальше ещё интереснее:
[] == false // true
Типы разные — один object
([]), другой boolean
, значит JS делает приведение. Сначала false
в число (false → 0), затем массив в примитив ([].toString()
), в итоге получается пустая строка. А пустая строка и 0 — это true
.
Следующий пример:
[] == '' // true
Здесь у нас объект приводится к примитиву, снова получается пустая строка, которая сравнивается с пустой же строкой — и в итоге мы получаем true
.
А вот здесь пустая строка приводится к числу 0, а два числа 0 дают true
:
0 == '' // true
👉 Очень важно всегда проверять, какие типы вы сравниваете. И по возможности использовать строгое сравнение (===), которое не допускает преобразований. Вообще, если видите в коде ==, задайте себе вопрос: точно ли здесь должно происходить приведение типов? Если нет — замените на ===.
Алгоритмы сравнения
А, собственно, почему всё так? Почему при приведении false
становится числом, а объект — строкой? Когда вы пишете ==, движок JavaScript запускает алгоритм абстрактного сравнения — со множеством шагов, описанный в спецификации ECMAScript.
Упрощённо это работает так:
- Если типы одинаковые — сравниваются значения.
- Если один из операндов — null, а другой — undefined, результат true.
- Если один — строка, а другой число, то строка превращается в число.
- Если один — boolean, то он приводится к числу (true → 1, false → 0).
- Если один — объект, а другой примитив, то объект приводится к примитиву через valueOf() или toString().
JS не творит дичь просто так. Он строго следует формализованным правилам, которые местами выглядят как шутка из 90-х, но формально описаны и работают одинаково во всех движках.
Обработка реальных ситуаций
Теперь разберём три самых частых случая, где типы могут вызывать баги.
В реальной жизни данные приходят отовсюду: из форм, из API, из localStorage, из JSON — и чаще всего приходят не в том виде, в каком вы их ожидали. Именно поэтому важно понимать, как и где типы данных влияют на логику приложения.
Обработка данных формы
HTML-форма — это фабрика строк. Что бы пользователь ни выбрал, ни ввёл, ни отметил галочкой — вы получите строку. Даже если это выглядит как число:
Допустим, у нас есть форма, куда пользователь вводит возраст:
<input type="number" name="age" value="30" />
Дальше мы обрабатываем данные из формы:
const formData = new FormData(form);
const age = formData.get('age');
console.log(typeof age); // 'string'
И да, даже если пользователь ввёл 30, typeof age
будет ‘string’
. И если сразу начнёте с этим числом что-то делать, не проверив и не приведя тип, будут ошибки.
Поэтому данные явно приводим к нужному нам типу, в данном случае к числу:
const age = Number(formData.get('age'));
Обработка данных JSON
JSON по сути просто строка, из которой JSON.parse()
делает объекты. Но и тут есть нюансы.
Допустим, мы получаем объект с возрастом и ролями пользователей:
const data = '{"age": "30", "isAdmin": "false"}';
const parsed = JSON.parse(data);
console.log(typeof parsed.age); // 'string'
console.log(typeof parsed.isAdmin); // 'string'
И сюрприз: даже булево значение пришло строкой. Почему? Потому что JSON по спецификации не знает про булевы строки типа “false”
. Он знает только про настоящие true
и false
без кавычек. А значит, кто-то на бэкенде криво сериализовал данные и решил, что “false”
в кавычках — это нормально.
Дальше возникает баг:
if (parsed.isAdmin) {
// и оно срабатывает, потому что непустая строка — true
}
Получается, по данным человек не админ, но условие срабатывает.
Поэтому сначала делаем строгое сравнение:
const isAdmin = parsed.isAdmin === 'true';
// теперь isAdmin — это boolean: true или false
И уже с ним работаем как с нормальной булевой переменной:
if (isAdmin) {
// сработает, только если реально true
}
А если таких полей много, то лучше написать отдельную функцию:
function normalizeUser(data) {
return {
// '30' → 30 (number)
age: Number(data.age),
// 'true'/'false' → boolean
isAdmin: data.isAdmin === 'true',
// можно добавить и другие поля
};
}
const user = normalizeUser(parsed);
JSON — это всегда зона риска. Даже если в документации написано, что isAdmin
— булево значение, на практике могут прислать “true”, “1”, “да” или просто null. Поэтому никогда нельзя не доверять данным, даже если их отправляете сами.
Интерпретация ввода пользователя
Та же история с чекбоксами — они хоть и выглядят как true/false
, но возвращают не булево значение, а ‘on’, null или вообще ‘1’, если форма нестандартная.
Например есть такой чекбокс, где нужно поставить галочку на согласие:
<form id="myForm">
<input type="checkbox" name="agree">
</form>
Если мы поставим галку, нажмём «Отправить» и выведем тип значения в консоль, то увидим, что это строка:
// Получаем ссылку на форму по id
const form = document.getElementById('myForm');
// Вешаем обработчик отправки формы
form.addEventListener('submit', (e) => {
// Отменяем стандартную отправку формы (без перезагрузки страницы)
e.preventDefault();
// Создаём объект FormData — он собирает все данные из формы
const formData = new FormData(form);
// Получаем значение чекбокса по имени "agree"
const value = formData.get('agree');
// Если галка стоит — 'on', если нет — null
console.log('Значение:', value);
// 'string' если 'on', 'object' если null (да, typeof null === 'object')
console.log('Тип:', typeof value);
});
Если чекбокс не отмечен, formData.get(‘agree’)
вернёт null
. Если отмечен, то вернёт строку ‘on’
. Да-да, не true
, не 1, а именно ‘on’
, потому что это дефолтное поведение HTML.
А теперь представим, что мы пишем что-то вроде:
if (formData.get('agree')) {
// пользователь согласен
}
И вроде всё норм пока значение ‘on’
. Но если внезапно прилетает ‘1’
, ‘yes’
или ‘true’
, то где-то там в глубине формочка мутировала, и теперь вся логика может сломаться.
Поэтому, если мы хотим просто получить true/false
, используем явное преобразование:
const isChecked = Boolean(formData.get('agree'));
А если хотим убедиться, что значение строго ‘on’
, то сравниваем явно:
const isChecked = formData.get('agree') === 'on';
Распространённые ошибки и ловушки
Работая с типами в JavaScript, легко попасть в ловушку. В этом блоке разберём наиболее частые грабли: NaN, null и определение массивов и объектов.
Тип NaN и его особенности
Сокращение NaN означает Not-a-Number — но при этом это тип number
:
typeof NaN; // 'number'
Что ещё абсурднее, NaN не равен даже сам себе:
NaN === NaN; // false
Поэтому этот тип проверяют через специальную функцию:
Number.isNaN(value); // правильный способ
Всё это особенно опасно, когда вы парсите числовые значения из ввода пользователя или API. Например, ждёте price
, а туда прилетело undefined
или ‘abc’
— вы делаете Number(price)
и получаете NaN. И дальше всё сломается:
if (price > 100) { ... } // и тут всё очень плохо
Или, например, при вычислениях:
const discount = total - Number(couponValue);
Если couponValue
не число, вы получаете NaN, а потом по цепочке всё превращается в NaN: total → NaN → finalPrice → NaN.
Так что, если в проекте есть хоть какие-то числовые операции с вводом пользователя, всегда валидируйте и проверяйте на NaN через Number.isNaN()
, прежде чем идти дальше.
Тип null и его особенности
null
— это отдельный примитив, означающий намеренное отсутствие значения. Но вот как мы уже писали, по историческим причинам null
— это object
. А ещё null легко путается с undefined
, особенно если использовать нестрогое сравнение:
null == undefined; // true — особое правило
null === undefined; // false — строго, как надо
Важно запомнить, что undefined
— это «значение не задано», а null
— «значение задано как отсутствующее». Разница может быть тонкой, но критичной.
Теперь к реальному сценарию. Допустим, вы получаете данные от бэкенда, где фамилия пользователя может быть null
, undefined
, пустой строкой или ‘0’ (для тестовых случаев) — и пишете:
if (!user.lastName) {
showWarning('Фамилия не указана');
}
На первый взгляд работает. Но if
— это не == и не ===. Он использует логическое приведение к true/false
. В результате условие срабатывает и там, где не должно — и валидная фамилия ‘0’ (например, у пользователя из тестовой базы) отклоняется как «не указана».
Поэтому важно использовать строгую проверку:
if (user.lastName === undefined || user.lastName === null || user.lastName === '') {
showWarning('Фамилия не указана');
}
В общем, null
и undefined
— это не просто «ничего». Это два разных «ничего», и обращаться с ними нужно осторожно, особенно когда дело касается данных из API, форм и фильтров. И всегда уточняйте, что именно вы ждёте от поля: оно отсутствует? Оно задано, но пустое? Или оно = 0?
Типы для массивов и объектов
В JavaScript массив — это частный случай объекта, только у него есть числовые ключи и встроенные методы. Но для движка это всё равно object
:
typeof [] // 'object'
Допустим, мы пишем обёртку над API, откуда приходит поле items
:
if (typeof items === 'object') {
items.forEach(item => doSomething(item));
}
Разработчик думает: items
— во множественном числе, значит, наверное, массив. А typeof
говорит ‘object’
— ну вроде сходится, можно итерировать.
Но в рантайме вылетает TypeError: items.forEach is not a function. А всё потому, что items
оказался не массивом, а обычным объектом. А может, это вообще null
, у которого typeof
тоже ‘object’.
Тип object
может быть чем угодно: {}, [], null, даже new Date(). Поэтому если мы хотим работать с массивом, то нужно явно спрашивать, что перед нами такое:
if (Array.isArray(items)) {
items.forEach(...);
} else {
// делаем, если это не массив
}
Array.isArray()
— это единственный надёжный способ отличить массив от других объектов.
Это важно, поскольку почти в любом приложении вам придётся обрабатывать JSON, где может быть либо [], либо {}, проверять входные данные из форм и работать с данными из сторонних библиотек или fetch-запросов. И если тупо проверять typeof
, не различая массив и объект, — будут баги.
Вам слово
Приходите к нам в соцсети поделиться своим мнением о статье и почитать, что пишут другие. А ещё там выходит дополнительный контент, которого нет на сайте — шпаргалки, опросы и разная дурка. В общем, вот тележка, вот ВК — велком!