Skip to main content

Типи даних у JavaScript

Типи даних у JavaScript поділяються на дві групи: сім примітивів (незмінні, передаються за значенням) та об'єкти (змінні, передаються за посиланням).

Теорія

TL;DR

  • Примітив, це як надрукована листівка: копіюєш і отримуєш незалежний дублікат.
  • Об'єкт, це як спільний Google Doc: усі «копії» вказують на одні й ті самі дані.
  • Сім примітивів: undefined, null, boolean, number, string, symbol, bigint.
  • Об'єкти охоплюють звичайні об'єкти, масиви, функції, дати, регулярні вирази та інше.
  • typeof null повертає "object", а не "null". Відомий баг з першої версії JavaScript.

Швидкий приклад

javascript
// Примітиви: присвоєння копіює значення let a = 5; let b = a; // b отримує власну копію значення 5 b = 10; console.log(a); // 5, не змінилось // Об'єкти: присвоєння копіює посилання let x = { val: 5 }; let y = x; // y вказує на той самий об'єкт y.val = 10; console.log(x.val); // 10, x теж змінився

Ці два приклади пояснюють більшість багів, пов'язаних з цією темою.

Головна різниця

Коли присвоюєш примітив, JavaScript копіює саме значення в окрему ділянку пам'яті. Дві змінні стають незалежними. З об'єктами інакше: змінна тримає посилання (адресу в пам'яті), а не самі дані. Присвоїш таку змінну іншій, і обидві вказуватимуть на один і той самий об'єкт у heap. Зміниш через одну, побачиш результат через обидві.

Коли що використовувати

  • Лічильник у циклі → примітив number.
  • Ім'я користувача → примітив string.
  • Профіль користувача з кількома полями → об'єкт.
  • Прапорець у стані React → примітив boolean.
  • Дані форми в стані React → об'єкт.
  • Цілі числа, більші за 2^53 - 1 → bigint.

Модель пам'яті

V8 зберігає примітиви у стеку (stack) для швидкого доступу. Об'єкти йдуть у heap, а у стеку живе лише вказівник. Саме цей вказівник копіюється під час присвоєння, тому дві змінні можуть непомітно ділити одні й ті самі дані. Object.create та схожі API напряму маніпулюють цими структурами в heap.

Типові помилки

Помилка 1: масив сприймають як примітив

javascript
let arr1 = [1, 2]; let arr2 = arr1; // копія посилання, не новий масив arr2.push(3); console.log(arr1); // [1, 2, 3], несподівано

Масиви є об'єктами. Виправлення: let arr2 = [...arr1] або arr1.slice().

Помилка 2: спроба змінити рядок посимвольно

javascript
let s = "hello"; s[0] = "H"; // помилки немає, але нічого не змінюється console.log(s); // "hello"

Рядки незмінні. Щоб замінити символ: s = "H" + s.slice(1).

Помилка 3: порівняння об'єктів через ===

javascript
console.log({} === {}); // false, різні посилання console.log([1] === [1]); // false

Об'єкти порівнюються за посиланням, а не за вмістом. Для структурної перевірки використовуй JSON.stringify або lodash.isEqual. Про те, як поводиться == з різними типами, читай у статті про перетворення типів у JavaScript.

Помилка 4: typeof null

javascript
typeof null === "object" // true typeof null === "null" // false

Це баг з JavaScript 1.0 у Netscape. Внутрішній тег типу для null збігся з тегом для об'єктів. Пропозиції виправити провалились через зворотну сумісність. Перевіряй null явно: value === null.

Помилка 5: BigInt у JSON

javascript
const id = 123n; JSON.stringify(id); // TypeError

JSON не підтримує BigInt. Конвертуй перед серіалізацією: id.toString(), або передай функцію-замінник у JSON.stringify.

Де зустрічається в реальних проектах

  • React: пропси як об'єкти { userId: 123, name: "Alice" }; ключі списків як примітиви key={id}.
  • Express: req.body - об'єкт, розпарсений з JSON; res.status(200) приймає числовий примітив.
  • Node.js fs: err у колбеках - або null (примітив), або об'єкт Error.
  • Redux: стан - дерево об'єктів; тип дії (action type) - рядковий примітив "INCREMENT".
  • Lodash: _.cloneDeep(obj) глибоко копіює вкладені об'єкти без спільних посилань.

Питання на співбесіді

Q: Що станеться, якщо передати примітив у функцію і змінити його всередині?
A: Функція отримає копію. Зміни всередині не зачеплять оригінальну змінну. З об'єктами навпаки: функція отримує посилання і може змінити оригінал.

Q: Чому typeof null повертає "object"?
A: Це баг з першого релізу JavaScript у Netscape. Оригінальний тег типу для null збігся з тегом для об'єктів. Кілька пропозицій виправити провалились, бо зміна зламала б існуючі сайти.

Q: Назви всі сім примітивів.
A: undefined, null, boolean, number, string, symbol (ES2015), bigint (ES2020).

Q: Чим BigInt відрізняється від Number?
A: Number - 64-бітний float з межею безпечних цілих 2^53 - 1. BigInt підтримує цілі числа довільного розміру. Вони не перетворюються один в одного неявно, тому 1n + 1 кине TypeError.

Q (для досвідчених): Об'єкт передано в асинхронний колбек. До моменту виклику колбека об'єкт змінено ззовні. Що побачить колбек?
A: Змінену версію. Колбек тримає посилання на той самий об'єкт у heap, а не знімок стану. Це поширена причина багів із застарілими даними, коли спільний об'єкт змінюється між плануванням і виконанням колбека.

Приклади

Примітиви та об'єкти при передачі у функцію

javascript
function addTen(n) { n = n + 10; // змінює лише локальну копію } function addScore(obj) { obj.score = 100; // змінює сам об'єкт } let num = 5; addTen(num); console.log(num); // 5, не змінилось let user = { name: "Alice" }; addScore(user); console.log(user.score); // 100, змінилось

Передав примітив, і будь-яка мутація залишається всередині функції. Передав об'єкт, і виклик побачить кожну зміну. Я бачив, як це спантеличує junior-розробників, які думали, що JavaScript завжди робить копію аргументу.

Витягування примітиву з пропса в React

javascript
function UserProfile({ user }) { // Витягуємо рядковий примітив з об'єкта-пропса в локальний стан const [name, setName] = useState(user.name); // Редагування поля змінює лише локальний стан // user.name залишається незачепленим return <input value={name} onChange={e => setName(e.target.value)} />; }

useState(user.name) копіює рядкове значення, а не посилання на user. Локальні зміни ніколи не мутують пропс. Якби замість user.name у useState передавався сам user, будь-яка мутація цього об'єкта ділилась би з батьківським компонентом.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?