В Notion вы открываете настройки, меняете тему с тёмной на светлую — и вдруг тема меняется для всех пользователей сразу. Это именно та ошибка, которую делают новички: изменяют «копию» объекта, а на самом деле изменяют оригинал. Потому что объекты в JavaScript передаются по ссылке.
Когда вы присваиваете объект переменной, переменная хранит не сам объект, а адрес в памяти — ссылку на него. При присваивании копируется ссылка, а не данные.
// Примитивы — копируются по значению
let a = 5
let b = a
b = 10
console.log(a) // 5 — a не изменилась
// Объекты — копируются по ссылке
let settings1 = { theme: 'dark', lang: 'ru' }
let settings2 = settings1 // settings2 указывает на ТОТ ЖЕ объект
settings2.theme = 'light'
console.log(settings1.theme) // 'light' — изменился оригинал!Создаёт новый объект с копией свойств первого уровня:
const original = { name: 'Алексей', age: 28 }
// Способ 1: spread-оператор (предпочтительный)
const copy1 = { ...original }
// Способ 2: Object.assign
const copy2 = Object.assign({}, original)
copy1.name = 'Михаил'
console.log(original.name) // 'Алексей' — не изменилсяВложенные объекты всё равно передаются по ссылке:
const user = {
name: 'Алексей',
address: { city: 'Москва', zip: '101000' }
}
const copy = { ...user }
copy.name = 'Иван' // OK — не затрагивает original
copy.address.city = 'СПб' // МУТАЦИЯ! address — всё ещё та же ссылка
console.log(user.name) // 'Алексей' — не изменилось
console.log(user.address.city) // 'СПб' — изменилось!Способ 1: JSON — простой, но теряет функции, Date, undefined:
const deepCopy = JSON.parse(JSON.stringify(user))
deepCopy.address.city = 'Казань'
console.log(user.address.city) // 'СПб' — не изменилсяСпособ 2: structuredClone — современный стандарт, сохраняет Date и Map:
const deepCopy = structuredClone(user)Способ 3: ручное копирование через spread — когда нужен контроль:
const deepCopy = {
...user,
address: { ...user.address }
}Ошибка 1: мутация в функции
// Сломано: функция меняет исходный объект
function applyDiscount(product, percent) {
product.price = product.price * (1 - percent / 100) // МУТАЦИЯ!
return product
}
const laptop = { name: 'Ноутбук', price: 80000 }
const sale = applyDiscount(laptop, 10)
console.log(laptop.price) // 72000 — исходный изменился!
// Исправлено: возвращаем новый объект
function applyDiscount(product, percent) {
return { ...product, price: product.price * (1 - percent / 100) }
}Ошибка 2: поверхностная копия при вложенных данных
// Сломано:
const newUser = { ...user }
newUser.settings.theme = 'light' // изменит и user.settings.theme!
// Исправлено:
const newUser = { ...user, settings: { ...user.settings, theme: 'light' } }Ошибка 3: сравнение объектов через ===
// Сломано: сравниваются ссылки, а не содержимое
const a = { x: 1 }
const b = { x: 1 }
console.log(a === b) // false — разные объекты в памяти!
// Для сравнения содержимого:
console.log(JSON.stringify(a) === JSON.stringify(b)) // true{ ...defaultSettings, ...userSettings } — безопасное слияниеСистема настроек Notion-приложения без мутаций
// Настройки по умолчанию
const defaultSettings = {
theme: 'dark',
fontSize: 14,
editor: {
spellcheck: true,
lineNumbers: false,
tabSize: 2,
}
}
// Обновление настройки первого уровня — spread достаточно
function setTheme(settings, theme) {
return { ...settings, theme }
}
// Обновление вложенного свойства — нужен вложенный spread
function setEditorOption(settings, key, value) {
return {
...settings,
editor: { ...settings.editor, [key]: value }
}
}
const userSettings = setTheme(defaultSettings, 'light')
console.log(defaultSettings.theme) // 'dark' — не изменился
console.log(userSettings.theme) // 'light'
const finalSettings = setEditorOption(userSettings, 'lineNumbers', true)
console.log(userSettings.editor.lineNumbers) // false — не изменился
console.log(finalSettings.editor.lineNumbers) // true
// Глубокая копия через structuredClone
const saved = structuredClone(finalSettings)
saved.editor.tabSize = 4
console.log(finalSettings.editor.tabSize) // 2 — не изменилсяВ Notion вы открываете настройки, меняете тему с тёмной на светлую — и вдруг тема меняется для всех пользователей сразу. Это именно та ошибка, которую делают новички: изменяют «копию» объекта, а на самом деле изменяют оригинал. Потому что объекты в JavaScript передаются по ссылке.
Когда вы присваиваете объект переменной, переменная хранит не сам объект, а адрес в памяти — ссылку на него. При присваивании копируется ссылка, а не данные.
// Примитивы — копируются по значению
let a = 5
let b = a
b = 10
console.log(a) // 5 — a не изменилась
// Объекты — копируются по ссылке
let settings1 = { theme: 'dark', lang: 'ru' }
let settings2 = settings1 // settings2 указывает на ТОТ ЖЕ объект
settings2.theme = 'light'
console.log(settings1.theme) // 'light' — изменился оригинал!Создаёт новый объект с копией свойств первого уровня:
const original = { name: 'Алексей', age: 28 }
// Способ 1: spread-оператор (предпочтительный)
const copy1 = { ...original }
// Способ 2: Object.assign
const copy2 = Object.assign({}, original)
copy1.name = 'Михаил'
console.log(original.name) // 'Алексей' — не изменилсяВложенные объекты всё равно передаются по ссылке:
const user = {
name: 'Алексей',
address: { city: 'Москва', zip: '101000' }
}
const copy = { ...user }
copy.name = 'Иван' // OK — не затрагивает original
copy.address.city = 'СПб' // МУТАЦИЯ! address — всё ещё та же ссылка
console.log(user.name) // 'Алексей' — не изменилось
console.log(user.address.city) // 'СПб' — изменилось!Способ 1: JSON — простой, но теряет функции, Date, undefined:
const deepCopy = JSON.parse(JSON.stringify(user))
deepCopy.address.city = 'Казань'
console.log(user.address.city) // 'СПб' — не изменилсяСпособ 2: structuredClone — современный стандарт, сохраняет Date и Map:
const deepCopy = structuredClone(user)Способ 3: ручное копирование через spread — когда нужен контроль:
const deepCopy = {
...user,
address: { ...user.address }
}Ошибка 1: мутация в функции
// Сломано: функция меняет исходный объект
function applyDiscount(product, percent) {
product.price = product.price * (1 - percent / 100) // МУТАЦИЯ!
return product
}
const laptop = { name: 'Ноутбук', price: 80000 }
const sale = applyDiscount(laptop, 10)
console.log(laptop.price) // 72000 — исходный изменился!
// Исправлено: возвращаем новый объект
function applyDiscount(product, percent) {
return { ...product, price: product.price * (1 - percent / 100) }
}Ошибка 2: поверхностная копия при вложенных данных
// Сломано:
const newUser = { ...user }
newUser.settings.theme = 'light' // изменит и user.settings.theme!
// Исправлено:
const newUser = { ...user, settings: { ...user.settings, theme: 'light' } }Ошибка 3: сравнение объектов через ===
// Сломано: сравниваются ссылки, а не содержимое
const a = { x: 1 }
const b = { x: 1 }
console.log(a === b) // false — разные объекты в памяти!
// Для сравнения содержимого:
console.log(JSON.stringify(a) === JSON.stringify(b)) // true{ ...defaultSettings, ...userSettings } — безопасное слияниеСистема настроек Notion-приложения без мутаций
// Настройки по умолчанию
const defaultSettings = {
theme: 'dark',
fontSize: 14,
editor: {
spellcheck: true,
lineNumbers: false,
tabSize: 2,
}
}
// Обновление настройки первого уровня — spread достаточно
function setTheme(settings, theme) {
return { ...settings, theme }
}
// Обновление вложенного свойства — нужен вложенный spread
function setEditorOption(settings, key, value) {
return {
...settings,
editor: { ...settings.editor, [key]: value }
}
}
const userSettings = setTheme(defaultSettings, 'light')
console.log(defaultSettings.theme) // 'dark' — не изменился
console.log(userSettings.theme) // 'light'
const finalSettings = setEditorOption(userSettings, 'lineNumbers', true)
console.log(userSettings.editor.lineNumbers) // false — не изменился
console.log(finalSettings.editor.lineNumbers) // true
// Глубокая копия через structuredClone
const saved = structuredClone(finalSettings)
saved.editor.tabSize = 4
console.log(finalSettings.editor.tabSize) // 2 — не изменилсяВ корзине интернет-магазина есть функция updateQuantity(cart, productId, qty). Напиши её так, чтобы она возвращала новый объект корзины с обновлённым количеством нужного товара, НЕ изменяя исходную корзину. Оригинальный cart должен остаться нетронутым.
Используй spread для корзины и map для массива items: items: cart.items.map(item => item.id === productId ? { ...item, qty } : item)