← Назад к вопросам

Как заморозить объект?

2.0 Middle🔥 161 комментариев
#JavaScript Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как заморозить объект

Замораживание объекта — это техника, которая делает объект неизменяемым. В JavaScript есть несколько способов разной глубины и функциональности. Расскажу про все подходы.

1. Object.freeze() — самый простой способ

const user = { name: 'Alice', age: 30 };

Object.freeze(user);

// Пытаюсь изменить
user.age = 31;  // В strict mode: TypeError, в обычном режиме молча игнорируется
user.email = 'alice@example.com'; // То же самое
delete user.name; // Не работает

console.log(user); // { name: 'Alice', age: 30 } — не изменился

// Проверяю, заморожен ли объект
console.log(Object.isFrozen(user)); // true

Что происходит при freeze:

  • Нельзя добавлять свойства
  • Нельзя удалять свойства
  • Нельзя изменять существующие свойства
  • Нельзя переконфигурировать property descriptors

Проблема: это shallow freeze!

const user = {
  name: 'Alice',
  address: { city: 'NYC' }
};

Object.freeze(user);

// Попытка изменить top-level
user.name = 'Bob'; // Не работает

// Но вложенный объект не заморожен!
user.address.city = 'LA'; // РАБОТАЕТ!
console.log(user); // { name: 'Alice', address: { city: 'LA' } }

2. Object.seal() — более мягкий способ

const user = { name: 'Alice', age: 30 };

Object.seal(user);

// Можно МЕНЯТЬ существующие свойства
user.age = 31; // РАБОТАЕТ

// Но нельзя ДОБАВЛЯТЬ новые
user.email = 'alice@example.com'; // Не работает

// И нельзя УДАЛЯТЬ
delete user.age; // Не работает

console.log(user); // { name: 'Alice', age: 31 } — возраст изменился
console.log(Object.isSealed(user)); // true

Отличие от freeze:

  • freeze: нельзя читать и писать
  • seal: можно писать, но нельзя добавлять/удалять ключи

3. Object.preventExtensions() — самый мягкий способ

const user = { name: 'Alice' };

Object.preventExtensions(user);

// Можно менять существующие свойства
user.name = 'Bob'; // РАБОТАЕТ

// Можно удалять
delete user.name; // РАБОТАЕТ

// Но нельзя добавлять новые
user.age = 30; // Не работает

console.log(user); // { name: 'Bob' }
console.log(Object.isExtensible(user)); // false

4. Deep freeze — полное замораживание вложенных объектов

function deepFreeze(obj) {
  // Сначала замораживаем сам объект
  Object.freeze(obj);
  
  // Затем рекурсивно замораживаем все вложенные объекты
  Object.values(obj).forEach(value => {
    if (typeof value === 'object' && value !== null) {
      deepFreeze(value);
    }
  });
  
  return obj;
}

const user = {
  name: 'Alice',
  address: {
    city: 'NYC',
    country: { name: 'USA' }
  },
  hobbies: ['reading', 'coding']
};

deepFreeze(user);

// Попытка изменить вложенный объект
user.address.city = 'LA'; // Не работает

// Попытка изменить массив
user.hobbies[0] = 'gaming'; // Не работает
user.hobbies.push('sports'); // Не работает

console.log(user); // Всё осталось как было

5. Property Descriptors — полный контроль

const user = {};

// Добавляю свойство с полным контролем
Object.defineProperty(user, 'age', {
  value: 30,
  writable: false,      // Нельзя изменить
  enumerable: true,     // Видно в for...in
  configurable: false   // Нельзя переконфигурировать
});

// Пытаюсь изменить
user.age = 31; // Не работает (в strict mode — ошибка)
delete user.age; // Не работает (configurable: false)

// Пытаюсь переконфигурировать
Object.defineProperty(user, 'age', { value: 31 }); // TypeError!

console.log(user.age); // 30

Различные combinations:

const config = {};

// Read-only свойство
Object.defineProperty(config, 'apiKey', {
  value: 'secret-key-123',
  writable: false,
  configurable: false
});

// Скрытое свойство (не видно в for...in)
Object.defineProperty(config, 'internalId', {
  value: 'internal-123',
  enumerable: false,
  writable: false
});

// Свойство с getter/setter
Object.defineProperty(config, 'theme', {
  get() {
    return this._theme || 'light';
  },
  set(value) {
    if (['light', 'dark'].includes(value)) {
      this._theme = value;
    }
  },
  enumerable: true,
  configurable: true
});

console.log(config.theme); // 'light'
config.theme = 'dark'; // OK
config.theme = 'invalid'; // Игнорируется

6. Использование в React

// Чтобы убедиться, что state не мутируется
const initialState = Object.freeze({
  users: [],
  loading: false,
  error: null
});

function reducer(state = initialState, action) {
  switch (action.type) {
    case 'SET_USERS':
      // Нельзя случайно заморозить state
      // state.users = action.payload; // TypeError если в strict mode
      
      // Правильный способ — вернуть новый объект
      return { ...state, users: action.payload };
    
    case 'SET_LOADING':
      return { ...state, loading: action.payload };
    
    default:
      return state;
  }
}

7. Проверка уровня заморозки

const obj = { a: 1 };
const sealedObj = { b: 2 };
const normalObj = { c: 3 };

Object.freeze(obj);
Object.seal(sealedObj);

// Проверка статуса
console.log(Object.isFrozen(obj)); // true
console.log(Object.isSealed(obj)); // true (frozen объект тоже sealed)
console.log(Object.isExtensible(obj)); // false

console.log(Object.isSealed(sealedObj)); // true
console.log(Object.isFrozen(sealedObj)); // false
console.log(Object.isExtensible(sealedObj)); // false

console.log(Object.isExtensible(normalObj)); // true

8. Практический пример: конфигурация

const CONFIG = Object.freeze({
  API_URL: 'https://api.example.com',
  TIMEOUT: 5000,
  MAX_RETRIES: 3,
  FEATURES: Object.freeze({
    AUTH: true,
    PAYMENTS: false,
    NOTIFICATIONS: true
  })
});

// Попытка изменить конфигурацию
CONFIG.API_URL = 'https://attacker.com'; // Не работает
CONFIG.FEATURES.PAYMENTS = true; // Не работает

// Конфигурация безопасна
console.log(CONFIG); // Оригинальные значения

9. Производительность

// Замораживание имеет минимальный overhead
const obj = { a: 1, b: 2 };

console.time('freeze');
for (let i = 0; i < 1000000; i++) {
  Object.freeze({ x: 1 });
}
console.timeEnd('freeze');
// freeze: ~50ms (на современных браузерах)

// Доступ к frozen объектам может быть немного быстрее
// (браузер знает, что объект не изменится)
const frozen = Object.freeze({ x: 1 });
const mutable = { x: 1 };

console.time('frozen access');
for (let i = 0; i < 100000000; i++) {
  frozen.x;
}
console.timeEnd('frozen access');
// В некоторых случаях замороженный доступ быстрее благодаря оптимизации

10. Когда использовать

Object.freeze():

  • Конфигурации (не должны меняться)
  • Константы (объекты вместо примитивов)
  • Immutable паттерны в Redux
  • Когда нужна полная защита от мутации

Object.seal():

  • Когда объект может менять существующие свойства
  • Но новые свойства не должны добавляться
  • Реже используется

Object.preventExtensions():

  • Когда только нужно предотвратить добавление новых ключей
  • Очень редко

defineProperty():

  • Когда нужен полный контроль
  • Read-only флаги
  • Computed properties (getters/setters)
  • Скрытые свойства

11. Ограничения

// freeze НЕ защищает от:
const obj = Object.freeze({ arr: [1, 2, 3] });

// Изменение вложенного массива
obj.arr[0] = 999; // РАБОТАЕТ!
console.log(obj); // { arr: [999, 2, 3] }

// Решение: deep freeze
function deepFreeze(obj) {
  Object.freeze(obj);
  Object.values(obj).forEach(v => {
    if (typeof v === 'object' && v !== null) {
      deepFreeze(v);
    }
  });
  return obj;
}

const obj2 = deepFreeze({ arr: [1, 2, 3] });
obj2.arr[0] = 999; // Не работает

Сравнительная таблица

МетодДобавлять свойстваМенять свойстваУдалять свойстваГлубина
freezeНетНетНетShallow
sealНетДаНетShallow
preventExtensionsНетДаДаShallow
deepFreezeНетНетНетDeep
definePropertyКонтрольКонтрольКонтрольSingle property

Ключевой вывод

Для замораживания объекта:

  1. Object.freeze() — для полной неизменяемости

    const obj = Object.freeze({ ... });
    
  2. Object.seal() — если нужно менять существующие свойства

    Object.seal(obj);
    
  3. Object.preventExtensions() — только если нужно предотвратить добавление

    Object.preventExtensions(obj);
    
  4. deepFreeze() — для вложенных объектов

    function deepFreeze(obj) { ... }
    
  5. Object.defineProperty() — для максимального контроля

    Object.defineProperty(obj, 'key', { ... });
    
Как заморозить объект? | PrepBro