Типы данных в 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, не различая массив и объект, — будут баги.
Вам слово
Приходите к нам в соцсети поделиться своим мнением о статье и почитать, что пишут другие. А ещё там выходит дополнительный контент, которого нет на сайте — шпаргалки, опросы и разная дурка. В общем, вот тележка, вот ВК — велком!
						


