Тип object — нюанси і пастки
Чому null — object, масиви — object, і як не зламати код при копіюванні
Object — рюкзак з підписаними кишенями
Примітив (число, рядок) — це одна проста річ: монета, ключ. Object — це рюкзак де є кишені з назвами. Кишеня 'name' → 'Іван', кишеня 'age' → 25. Всередині можуть бути інші рюкзаки. Це і є головна особливість — object зберігає СТРУКТУРОВАНІ дані з іменованими полями.
Що таке object насправді
Object — це колекція пар ключ: значення. Ключі — це рядки (або Symbol), значення — будь-який тип.
const user = {
name: "Іван", // ключ: 'name', значення: рядок
age: 25, // ключ: 'age', значення: число
isStudent: true, // ключ: 'isStudent', значення: boolean
address: { // ключ: 'address', значення: інший object!
city: "Київ",
zip: "01001",
},
};
// Доступ до властивостей
console.log(user.name); // "Іван" — крапкова нотація
console.log(user["name"]); // "Іван" — дужкова нотація
console.log(user.address.city); // "Київ" — вкладений доступНюанс №1: typeof null === "object"
Це одна з найвідоміших помилок (баг) в JavaScript:
console.log(typeof null); // "object" ← ПОМИЛКА В САМОМУ JS
console.log(typeof {}); // "object" — тут все правильно
console.log(typeof []); // "object" — масиви теж object!typeof null === "object" — це баг від 1995 року (перший тиждень існування JavaScript). Null — не object, але виправити вже неможливо: занадто багато старого коду це використовує.
Як правильно перевірити null:
// НЕ ПРАВИЛЬНО:
if (typeof value === "object") { ... } // поймає і null!
// ПРАВИЛЬНО:
if (value !== null && typeof value === "object") { ... }Нюанс №2: Масиви — теж object
let fruits = ["яблуко", "банан"];
console.log(typeof fruits); // "object" — не "array"!
console.log(Array.isArray(fruits)); // true — ось правильна перевірка
console.log(Array.isArray({})); // false
// Чому так? Масив в JS — це object зі спеціальними числовими ключами:
let arr = ["a", "b", "c"];
// Насправді це: { 0: "a", 1: "b", 2: "c", length: 3 }
console.log(arr[0]); // "a"
console.log(arr.length); // 3Правило: щоб перевірити масив — завжди Array.isArray(value), не typeof.
Нюанс №3: Примітив vs Посилання (Reference)
Це НАЙВАЖЛИВІШИЙ нюанс object. Примітиви зберігають значення, object зберігає посилання на місце в пам'яті.
// Примітиви — копіюються за значенням
let a = 5;
let b = a; // b отримує КОПІЮ значення 5
b = 10;
console.log(a); // 5 — а НЕ змінився!
// Object — копіюються за посиланням
let user1 = { name: "Іван" };
let user2 = user1; // user2 вказує на ТОЙ САМИЙ об'єкт!
user2.name = "Марія";
console.log(user1.name); // "Марія" — user1 ТЕЖ змінився!user2 = user1 не копіює об'єкт — лише передає адресу в пам'яті. Обидві змінні вказують на одне й те саме місце.
{ ...original } — spread operator. Він копіює ВСІ властивості в новий об'єкт. Це 'поверхнева копія' (shallow copy).
Нюанс №4: Порівняння object за посиланням
// Примітиви порівнюються за ЗНАЧЕННЯМ
console.log(5 === 5); // true — однакові значення
console.log("JS" === "JS"); // true
// Object порівнюється за ПОСИЛАННЯМ (адресою в пам'яті)
console.log({} === {}); // false — різні об'єкти!
console.log([] === []); // false — різні масиви!
let a = { x: 1 };
let b = a; // b вказує на той самий об'єкт
console.log(a === b); // true — та сама адреса!{} === {} — false, бо це два різні об'єкти в різних місцях пам'яті, навіть якщо вони однакові.
Як порівняти вміст? Перетвори в рядок: JSON.stringify(obj1) === JSON.stringify(obj2)
Нюанс №5: Shallow copy vs Deep copy
// Shallow copy (поверхнева) — spread
let user = { name: "Іван", address: { city: "Київ" } };
let copy = { ...user };
copy.name = "Марія"; // OK — примітив скопіювався
copy.address.city = "Одеса"; // НЕБЕЗПЕЧНО!
console.log(user.name); // "Іван" — не змінився ✓
console.log(user.address.city); // "Одеса" — ЗМІНИВСЯ! ✗
// Spread скопіював тільки перший рівень!
// address — вкладений об'єкт, він передався за посиланням.
// Deep copy (глибока) — через JSON
let deepCopy = JSON.parse(JSON.stringify(user));
deepCopy.address.city = "Харків";
console.log(user.address.city); // "Одеса" — не змінився ✓Spread ({...obj}) — поверхнева копія (shallow). Для вкладених об'єктів — JSON.parse(JSON.stringify(obj)) або structuredClone(obj) (сучасний метод).
Дві найпоширеніші помилки з object: 1. let copy = obj — думаєш що скопіював, але насправді обидві змінні вказують на один об'єкт. 2. typeof null === 'object' — перевіряєш тип і забуваєш виключити null. Заучи назубок: для копії — spread {...obj}, для перевірки на null — value !== null.
structuredClone() — вбудована функція для глибокого копіювання. Підтримується у всіх сучасних браузерах.