Перейти к основному содержимому

5.01. Типы данных в JS

Разработчику Архитектору

Типы данных в JS

Типы данных и типизация

Переменные и константы в процессе использования, получают какое-то значение. Когда мы указываем, что у нас будет возраст (age), мы понимаем, что это будет какое-то число в диапазоне от 0 до 150, к примеру. А для имени (name) будет уже довольно обширный набор символов с разным количеством – от одного до пятидесяти (а может, и больше?). И для устройства это важно, определить участок памяти для небольшого значения (age) или большого (name) – и это определяется типом данных, который даёт системе указание выделить нужный объем ресурсов.

★ JavaScript – язык с динамической типизацией (тип переменной определяется автоматически), поэтому можно просто объявить переменную, не указывая, какого типа она будет, и система сама поймёт, с каким типом работает. Мы просто можем написать «пусть будет x» - «let x», и когда будет x=10, динамическая типизация позволит определить, что тут хватит Number, если же x=”Какой-то текст”, то небольшим размером Number не обойтись, и тип будет указан как String.

Типы в JS бывают примитивными (хранят одно значение) и ссылочными (хранят сложные структуры).


Примитивные типы

ТипОписаниеПример
NumberЧисла (целые и дробные)let age = 25;
StringСтроки (текст)let name = "Анна";
BooleanЛогический (true / false)let isStudent = true;
UndefinedЗначение не присвоеноlet x;
NullПустое значениеlet y = null;
SymbolУникальный идентификаторconst id = Symbol("id");

Как можно понять, примитивные используются для простых данных, когда это какое-то имя, число, текст, или просто «флажок» вроде «истина» или «ложь».


Число

Число (Number). Тип Number представляет как целые, так и вещественные числа. Внутренне используется формат IEEE 754 (64-битное число с плавающей точкой). Поддерживает специальные значения: Infinity, -Infinity, NaN.

Простой пример:

let age = 30;

Сложный пример:

const totalPrice = items.reduce((sum, item) => sum + (item.price * item.quantity), 0)
.toFixed(2);
const finalPrice = parseFloat(totalPrice) + (hasDiscount ? -totalPrice * 0.1 : 0);

Здесь значение переменной finalPrice формируется через цепочку операций: агрегация массива объектов (reduce), округление до двух знаков после запятой (toFixed — возвращает строку), преобразование строки обратно в число (parseFloat) и условная скидка. Результат — число, но его получение требует нескольких этапов обработки.


Булево

Булево (Boolean) принимает два значения: true и false. Также может быть получен неявно через приведение типов (например, !!"text" → true).

Простой пример:

let isActive = true;

Сложный пример:

const isEligible = user.age >= 18 && 
user.hasVerifiedEmail &&
(user.permissions.includes('read') || user.role === 'admin');

Переменная isEligible принимает булево значение, вычисленное на основе нескольких условий: возраст, статус верификации и права доступа. Это типичный случай использования логических выражений для контроля доступа.


Строки

Строка (String). Тип String представляет собой последовательность символов UTF-16. Может создаваться как строковый литерал или через конструктор String(). Простой пример:

let name = "Тимур";

Сложный пример:

const greeting = `Добро пожаловать, ${user.name || 'Гость'}!
Вы вошли ${new Date().toLocaleDateString('ru-RU')} в ${new Date().getHours()}:${String(new Date().getMinutes()).padStart(2, '0')}.
Ваш баланс: ${(user.balance ?? 0).toFixed(2)} ₽.`;

Выше используется шаблонная строка с интерполяцией значений из объекта, операторами нулевого слияния (??) и логического ИЛИ (||), форматированием времени и числовым округлением. Результат — многострочная строка, собранная динамически.

Отдельно важно выделить symbol - в JS они особые, и не относятся к «тексту» вроде строки. Это ярлык, который можно использовать как ключ для свойств объекта, как наклейка на шкаф. Каждый Symbol — уникален, даже если у них одинаковое описание. Даже если два символа называются "id", они не равны. Это как два разных браслета с одинаковой гравировкой — выглядят одинаково, но физически разные.

Представим наглядно.

У нас есть объект user, и мы ему решили добавить свойство, сразу в формате строки:

user.id = "12345";

И в дальнейшем, эту переменную id можно перезаписать, заменив значение - если кто-то укажет user.id = значение, то наше изначальное значение будет перезаписано:

user.id = 23456;

И если мы не хотим, чтобы другие скрипты перезаписали случайно наш код, можем использовать Symbol:

user.id = Symbol("id");

user[id] = 12345;

Теперь наш id не перезапишется, потому что не знают о том, что есть id как Symbol. Символы невидимы в циклах, и не покажутся в переборе. Symbol — это скрытое свойство. Его нельзя автоматически превратить в строку при преобразовании типов.

Но в ряде случаев, можно ссылаться к каким-то сложным объектам, как мы ранее уже упоминали – это ссылочные типы.


Ссылочные типы

ТипОписаниеПример
ObjectОбъект (ключ: значение). Кстати, JSON это Java Script Object Notation, так что это, фактически, тот же синтаксис.const user = { name: "Анна" };
ArrayМассив (список значений)const nums = [1, 2, 3];
FunctionФункцияfunction greet() { ... }

Объект имеет какие-то свойства и значения, допустим name – свойство объекта user. Объект, как мы ранее выяснили, может иметь и свои функции – это будут методы.


Объект

Объект — это коллекция пар «ключ-значение». Является ссылочным типом. Все не-примитивы в JS являются объектами (включая массивы и функции).

Простой пример:

let person = { name: "Иван", age: 28 };

Сложный пример:

const config = Object.freeze({
apiUrl: process.env.API_URL || 'https://api.example.com',
timeout: parseInt(process.env.TIMEOUT || '5000', 10),
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${authToken}`
},
retryCount: maxRetries > 0 ? Math.min(maxRetries, 5) : 0
});

Выше в примере мы видим, как создаётся объект конфигурации с использованием переменных окружения, преобразованием типов, условной логикой и вложенностью. Затем он замораживается (Object.freeze) для предотвращения изменений — типичная практика в продвинутых приложениях.

Объекты могут быть базовыми, DOM, пользовательскими и глобальным.


Глобальный объект

Глобальный объект - это объект, который существует в любом окружении выполнения JavaScript и доступен всегда, везде. Все глобальные переменные, функции, встроенные константы на самом деле являются свойствами этого объекта.

Какой именно объект является глобальным, зависит от окружения:

ОкружениеГлобальный объект
Браузерwindow
Node.jsglobal
Современный JS (в любом окружении)globalThis

Как это работает? Такой глобальный объект содержит встроенные глобальные переменные, функции и объекты:

Свойства:

  • Infinity - бесконечность;
  • NaN - «не число»;
  • undefined - значение undefined;
  • globalThis - ссылка на самого себя.

Встроенные функции:

  • isNaN() - проверяет, является ли значение NaN;
  • isFinite() - проверяет, является ли значение конечным числом;
  • parseFloat(), parseInt() - парсинг чисел;
  • encodeURI(), decodeURI() - кодировка URL;
  • eval() - выполнение строки как кода.

Встроенные конструкторы (которые на самом деле - функции):

  • Object - объект;
  • Array - массив;
  • String - строка;
  • Number - число;
  • Boolean - булево значение;
  • Function - функция;
  • Date - дата;
  • RegExp - регулярное выражение;
  • Symbol - уникальный идентификатор;
  • Error, TypeError, SyntaxError - ошибки.

Встроенные объекты:

  • Math - математические функции;
  • JSON - работа с JSON;
  • Promise - промисы (позже их изучим);
  • console - консоль.

Если объявить переменную без var, let, const в глобальной области - она станет свойством глобального объекта. То есть не пишите вроде x = 42, если не хотите создавать глобальное свойство - добавляйте ключевое слово let или const.

Важно - let, const, class не создают свойства в глобальном объекте, а var создаёт, как и вышеприведенный пример с x = 42.


Массив

Массив же используется как набор данных, когда мы хотим расширить набор данных. К примеру, nums – набор чисел, включающий три значения – 1, 2 и 3. Каждый из этих элементов массива имеет индекс, что позволяет обращаться к конкретному значению, допустим, 2.

Массив (Array) — упорядоченная коллекция элементов. Является подтипом объекта, но имеет специфические методы (map, filter, reduce и т.д.).

Простой пример:

let numbers = [1, 2, 3];

Сложный пример:

const filteredUsers = users
.filter(user => user.active)
.map(user => ({
...user,
fullName: `${user.firstName} ${user.lastName}`.trim(),
roleLabel: roleMap[user.role] || 'Неизвестно'
}))
.sort((a, b) => a.fullName.localeCompare(b.fullName));

Здесь выполняется фильтрация активных пользователей, маппинг с расширением данных (включая объединение полей и использование внешнего маппинга ролей), затем сортировка по алфавиту. Результат — новый массив объектов с расширенной информацией.


Функция

Функция – это та сама функция, которые мы изучили ранее. Она тоже представляет собой тип данных.

Функция (Function) — это объект первого класса. Может быть передана как аргумент, возвращена из другой функции, храниться в переменной.

Простой пример:

function greet(name) {
return `Привет, ${name}!`;
}

Сложный пример:

const createValidator = (rules) => (data) => {
const errors = [];
for (const [field, rule] of Object.entries(rules)) {
if (!rule(data[field])) {
errors.push(`Поле "${field}" не прошло валидацию.`);
}
}
return errors.length === 0 ? null : errors;
};

const validateUser = createValidator({
email: (v) => /\S+@\S+\.\S+/.test(v),
age: (v) => v >= 18 && v < 120
});

Здесь реализован паттерн каррирования: функция высшего порядка возвращает другую функцию. createValidator абстрагирует логику валидации, позволяя генерировать конкретные валидаторы. Это мощный инструмент функционального программирования.


Автоматические преобразования (coercion)

JavaScript автоматически преобразует типы данных при выполнении операций. Это поведение называется приведением типов или автоматическим преобразованием.

Явное преобразование типов

Явное преобразование происходит, когда программист намеренно меняет тип данных с помощью встроенных функций.

Преобразование в число:

let str = "123";
let num = Number(str); // 123

let bool = true;
let numFromBool = Number(bool); // 1

let empty = "";
let numFromEmpty = Number(empty); // 0

Преобразование в строку:

let num = 456;
let str = String(num); // "456"

let bool = false;
let strFromBool = String(bool); // "false"

let obj = { name: "Джон" };
let strFromObj = String(obj); // "[object Object]"

Преобразование в булево значение:

let str = "текст";
let bool = Boolean(str); // true

let empty = "";
let boolEmpty = Boolean(empty); // false

let num = 0;
let boolNum = Boolean(num); // false

let obj = { name: "Джон" };
let boolObj = Boolean(obj); // true

Неявное преобразование типов

Неявное преобразование происходит автоматически при выполнении операций.

Преобразование при сложении:

let result = 5 + "5"; // "55" - число преобразуется в строку
let another = "Цена: " + 100; // "Цена: 100"

Преобразование при сравнении:

let isEqual = 5 == "5"; // true - неявное преобразование
let isStrictEqual = 5 === "5"; // false - строгое сравнение без преобразования

let isTrue = 0 == false; // true
let isFalse = 1 == true; // true

Преобразование при логических операциях:

let value = "привет" && 42; // 42 - первое истинное значение
let another = "" || "значение"; // "значение" - первое истинное значение

let result = 0 || null || undefined || "ок"; // "ок"

Таблица преобразований

ЗначениеВ числоВ строкуВ булево
"" (пустая строка)0""false
"0"0"0"true
"123"123"123"true
00"0"false
11"1"true
NaNNaN"NaN"false
null0"null"false
undefinedNaN"undefined"false
true1"true"true
false0"false"false
{}NaN"[object Object]"true
[]0""true

Ложные значения (falsy values)

В JavaScript существует шесть значений, которые преобразуются в false при булевом преобразовании:

  • false
  • 0
  • "" (пустая строка)
  • null
  • undefined
  • NaN

Все остальные значения преобразуются в true.

Примеры:

if (0) {
console.log("Это не выполнится");
}

if ("") {
console.log("Это тоже не выполнится");
}

if (null) {
console.log("И это не выполнится");
}

if (42) {
console.log("Это выполнится"); // 42 преобразуется в true
}

if ("текст") {
console.log("Это тоже выполнится"); // "текст" преобразуется в true
}

if ({}) {
console.log("И это выполнится"); // объект преобразуется в true
}

Практические примеры преобразований

Проверка ввода пользователя:

function validateInput(input) {
if (!input) {
return "Поле не может быть пустым";
}

const number = Number(input);
if (isNaN(number)) {
return "Введите число";
}

if (number <= 0) {
return "Число должно быть положительным";
}

return "Валидация пройдена";
}

console.log(validateInput("")); // "Поле не может быть пустым"
console.log(validateInput("abc")); // "Введите число"
console.log(validateInput("-5")); // "Число должно быть положительным"
console.log(validateInput("10")); // "Валидация пройдена"

Работа с формами:

function processForm(data) {
const age = Number(data.age) || 18; // Если возраст не указан, используем 18
const isActive = data.isActive === "true"; // Преобразуем строку в булево

return {
name: String(data.name).trim(),
age: age,
email: String(data.email).toLowerCase(),
isActive: isActive
};
}

const formData = {
name: " Джон ",
age: "25",
email: "JOHN@EXAMPLE.COM",
isActive: "true"
};

console.log(processForm(formData));
// { name: "Джон", age: 25, email: "john@example.com", isActive: true }

Объекты как ссылочные типы

В JavaScript объекты являются ссылочными типами данных. Это означает, что переменная хранит не само значение, а ссылку на место в памяти, где находится объект.

Разница между примитивными и ссылочными типами

Примитивные типы хранят значение напрямую:

let a = 10;
let b = a;
b = 20;

console.log(a); // 10 - значение не изменилось
console.log(b); // 20

Ссылочные типы хранят ссылку на объект:

let obj1 = { name: "Джон" };
let obj2 = obj1;
obj2.name = "Мария";

console.log(obj1.name); // "Мария" - объект изменился
console.log(obj2.name); // "Мария"

Передача по ссылке

При передаче объекта в функцию передаётся ссылка на объект, а не его копия:

function updateName(person) {
person.name = "Алекс";
}

const user = { name: "Джон" };
updateName(user);

console.log(user.name); // "Алекс" - объект изменился

Поверхностное копирование

Поверхностное копирование создаёт новый объект, но вложенные объекты копируются по ссылке:

const original = {
name: "Джон",
address: {
city: "Москва",
street: "Тверская"
}
};

// Способ 1: spread оператор
const copy1 = { ...original };
copy1.name = "Мария";
copy1.address.city = "Санкт-Петербург";

console.log(original.name); // "Джон" - изменилось только имя копии
console.log(original.address.city); // "Санкт-Петербург" - город изменился в обоих

// Способ 2: Object.assign
const copy2 = Object.assign({}, original);

Глубокое копирование

Глубокое копирование создаёт полностью независимую копию объекта со всеми вложенными объектами:

const original = {
name: "Джон",
address: {
city: "Москва",
street: "Тверская"
}
};

// Способ 1: через JSON
const deepCopy1 = JSON.parse(JSON.stringify(original));
deepCopy1.address.city = "Санкт-Петербург";

console.log(original.address.city); // "Москва" - оригинал не изменился
console.log(deepCopy1.address.city); // "Санкт-Петербург"

// Способ 2: рекурсивная функция
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') {
return obj;
}

if (Array.isArray(obj)) {
return obj.map(item => deepClone(item));
}

const cloned = {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = deepClone(obj[key]);
}
}
return cloned;
}

const deepCopy2 = deepClone(original);

Сравнение объектов

Объекты сравниваются по ссылке, а не по содержимому:

const obj1 = { name: "Джон" };
const obj2 = { name: "Джон" };
const obj3 = obj1;

console.log(obj1 === obj2); // false - разные объекты
console.log(obj1 === obj3); // true - одна и та же ссылка

// Для сравнения по содержимому нужна специальная функция
function areEqual(objA, objB) {
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);

if (keysA.length !== keysB.length) {
return false;
}

for (const key of keysA) {
if (objA[key] !== objB[key]) {
return false;
}
}

return true;
}

console.log(areEqual(obj1, obj2)); // true - содержимое одинаковое

Изменяемость объектов

Объекты в JavaScript являются изменяемыми (mutable):

const user = {
name: "Джон",
age: 30
};

user.age = 31; // Можно изменить свойство
user.email = "john@example.com"; // Можно добавить новое свойство
delete user.age; // Можно удалить свойство

console.log(user);
// { name: "Джон", email: "john@example.com" }

Для создания неизменяемых объектов используется Object.freeze:

const frozenUser = Object.freeze({
name: "Джон",
age: 30
});

frozenUser.age = 31; // Не сработает в строгом режиме
frozenUser.email = "john@example.com"; // Не сработает

console.log(frozenUser.age); // 30 - значение не изменилось

Практические примеры работы со ссылками

Клонирование массива объектов:

const users = [
{ id: 1, name: "Джон" },
{ id: 2, name: "Мария" },
{ id: 3, name: "Алекс" }
];

// Поверхностное копирование массива
const shallowCopy = [...users];
shallowCopy[0].name = "Иван";

console.log(users[0].name); // "Иван" - оригинал изменился

// Глубокое копирование массива
const deepCopy = users.map(user => ({ ...user }));
deepCopy[0].name = "Петр";

console.log(users[0].name); // "Иван" - оригинал не изменился
console.log(deepCopy[0].name); // "Петр"

Функция, которая не изменяет исходный объект:

function updateUser(user, updates) {
return {
...user,
...updates
};
}

const originalUser = { id: 1, name: "Джон", age: 30 };
const updatedUser = updateUser(originalUser, { age: 31 });

console.log(originalUser.age); // 30 - оригинал не изменился
console.log(updatedUser.age); // 31 - новая версия с изменениями

Работа с вложенными структурами:

function updateNestedProperty(obj, path, value) {
const keys = path.split('.');
const result = { ...obj };
let current = result;

for (let i = 0; i < keys.length - 1; i++) {
current[keys[i]] = { ...current[keys[i]] };
current = current[keys[i]];
}

current[keys[keys.length - 1]] = value;
return result;
}

const state = {
user: {
profile: {
name: "Джон",
settings: {
theme: "light"
}
}
}
};

const newState = updateNestedProperty(state, "user.profile.settings.theme", "dark");

console.log(state.user.profile.settings.theme); // "light" - оригинал не изменился
console.log(newState.user.profile.settings.theme); // "dark" - новая версия