const захищає лише змінну від переприсвоєння, але не вміст об'єкта. Для захисту самого об'єкта є три рівні:
| Метод | Додати властивість | Видалити | Змінити значення |
|---|---|---|---|
Object.preventExtensions |
ні | так | так |
Object.seal |
ні | ні | так |
Object.freeze |
ні | ні | ні |
const config = Object.freeze({ apiUrl: '/api', retries: 3 });
config.retries = 10; // тихо ігнорується
delete config.apiUrl; // тихо ігнорується
config.retries; // 3
Тихо - лише в нестрогому режимі. У строгому режимі (а ES-модулі та класи завжди строгі) спроба змінити заморожений об'єкт кидає TypeError. Це добре: помилка видна одразу.
Заморожування поверхневе:
const settings = Object.freeze({ theme: 'dark', limits: { upload: 10 } });
settings.limits.upload = 1000; // працює!
Вкладені об'єкти лишаються змінюваними. Для глибокого заморожування потрібна рекурсія:
function deepFreeze(object) {
for (const value of Object.values(object)) {
if (value && typeof value === 'object' && !Object.isFrozen(value)) {
deepFreeze(value);
}
}
return Object.freeze(object);
}
Інші нюанси:
- масиви теж можна заморозити:
push,sort, присвоєння елемента кидають помилку в строгому режимі; Map,Set,Dateзаморожування не захищає: їхні дані зберігаються у внутрішніх слотах, а не у властивостях.Object.freeze(new Map()).set('a', 1)спрацює;- розморозити неможливо - лише створити змінювану копію (
{ ...frozen },structuredClone); - перевірки:
Object.isFrozen,Object.isSealed,Object.isExtensible.
Де це корисно:
- константи й конфігурація, що не повинні змінюватися випадково;
- «перелічення» без TypeScript:
const Status = Object.freeze({ Paid: 'paid', New: 'new' }); - спільний стан, який мають змінювати лише через визначені функції - заморожування ловить випадкові мутації в розробці.
Що не варто робити: заморожувати все підряд «для іммутабельності». Реальну незмінність у застосунках зазвичай забезпечують підходом - нові об'єкти замість зміни старих (toSorted, spread), - а TypeScript з readonly ловить мутації ще на етапі компіляції без витрат під час виконання.