Сравнение методов ограничения объектов в JavaScript: что можно и нельзя делать с замороженными, запечатанными и нерасширяемыми объектами.

Ограничение объектов: preventExtensions, seal и freeze

JavaScript предоставляет три уровня «заморозки» объекта — от лёгкого запрета на добавление новых свойств до полной неизменяемости. Понимание разницы между ними помогает выбирать правильный уровень защиты данных.

В JavaScript объекты по умолчанию открыты для любых изменений: в них можно добавлять, удалять и перезаписывать свойства, а также менять их дескрипторы. Однако при разработке библиотек, работе с конфигурациями или реализации иммутабельного состояния (например, в Redux) такое поведение нежелательно. Для контроля над объектами в языке существуют три метода: Object.preventExtensions(), Object.seal() и Object.freeze().

Каждый из них накладывает определённые ограничения, причём каждый следующий является более строгим вариантом предыдущего. Разберём их по порядку, от самого мягкого к самому жёсткому.

Object.preventExtensions — запрет на расширение

Метод Object.preventExtensions(obj) запрещает добавлять новые свойства в объект. При этом все существующие свойства остаются полностью управляемыми: их можно изменять, удалять и перенастраивать их дескрипторы.

const config = { host: "localhost", port: 3000 };
Object.preventExtensions(config);

config.port = 8080;        // ✅ Значение изменилось
delete config.host;        // ✅ Свойство удалено
config.ssl = true;         //  Новое свойство не добавится
                           //    (в strict mode — TypeError)

console.log(Object.isExtensible(config)); // false

Проверить, можно ли расширять объект, помогает метод Object.isExtensible(obj). После вызова preventExtensions вернуть объекту расширяемость уже нельзя — это односторонняя операция.

Object.seal — запечатывание объекта

Метод Object.seal(obj) делает всё, что делает preventExtensions, и дополнительно запрещает удалять существующие свойства. При этом значения свойств по-прежнему можно менять, если они были writable.

Под капотом seal устанавливает всем свойствам флаг configurable: false. Это означает, что нельзя ни удалить свойство, ни изменить его дескриптор (например, превратить обычное свойство в геттер/сеттер).

const user = { name: "Ivan", age: 25 };
Object.seal(user);

user.age = 30;           // ✅ Значение изменилось
delete user.name;        // ❌ Удалить нельзя
user.city = "Moscow";    // ❌ Добавить нельзя

Object.defineProperty(user, "name", {
  writable: false        // ❌ Изменить дескриптор нельзя
});

console.log(Object.isSealed(user)); // true

Object.freeze — полная заморозка

Метод Object.freeze(obj) — самый строгий уровень защиты. Он делает всё, что делает seal, и дополнительно запрещает изменять значения свойств. Объект становится полностью неизменяемым (иммутабельным).

Под капотом freeze устанавливает всем свойствам флаги configurable: false и writable: false.

const constants = { PI: 3.14159, E: 2.71828 };
Object.freeze(constants);

constants.PI = 3.14;          // ❌ Значение не изменится
constants.NEW_PROP = 1;       // ❌ Добавить нельзя
delete constants.E;           // ❌ Удалить нельзя

console.log(Object.isFrozen(constants)); // true

Сводная таблица ограничений

Для наглядности все три метода можно сравнить в одной таблице. Чем ниже метод в списке, тем больше ограничений он накладывает.

Метод Добавление новых свойств Удаление свойств Изменение значений свойств Изменение атрибутов свойств
Object.preventExtensions ❌ ✅ ✅ ✅
Object.seal ❌ ❌ ✅ ❌
Object.freeze ❌ ❌ ❌

Логика проста: каждый следующий метод «наследует» ограничения предыдущего и добавляет свои. Поэтому freeze — это, по сути, seal + запрет на изменение значений, а seal — это preventExtensions + запрет на удаление и изменение дескрипторов.

Важный нюанс: поверхностное действие

Все три метода действуют только на первом уровне объекта. Если свойство содержит вложенный объект или массив, он остаётся изменяемым.

const data = {
  title: "Report",
  meta: { author: "Ivan" }
};
Object.freeze(data);

data.title = "New";          // ❌ Не изменится
data.meta.author = "Petr";   // ✅ Изменится! Вложенный объект не заморожен

Для полной (глубокой) заморозки нужно рекурсивно обойти все вложенные объекты и применить freeze к каждому из них.

function deepFreeze(obj) {
  if (obj === null || typeof obj !== "object") return obj;

  Object.freeze(obj);

  for (const key in obj) {
    if (Object.hasOwn(obj, key)) {
      deepFreeze(obj[key]);
    }
  }

  return obj;
}

const config = deepFreeze({ db: { host: "localhost" } });
config.db.host = "remote"; // ❌ Теперь и вложенное свойство защищено

Поведение в strict mode

В обычном режиме JavaScript при нарушении ограничений просто молча игнорирует операцию — свойство не изменится, но и ошибки не будет. Это частая причина трудноуловимых багов.

В строгом режиме ("use strict") любая запрещённая операция выбрасывает TypeError. Поэтому при работе с замороженными объектами всегда рекомендуется использовать strict mode.

"use strict";

const obj = Object.freeze({ x: 1 });
obj.x = 2; // TypeError: Cannot assign to read only property 'x'

Практическое применение

  • preventExtensions — когда нужно зафиксировать набор свойств объекта, но оставить возможность менять их значения. Например, форма с фиксированным набором полей.
  • seal — когда структура объекта важна, но данные внутри могут обновляться. Например, состояние сессии пользователя с фиксированным набором ключей.
  • freeze — для констант, конфигураций, словарей и любых данных, которые не должны меняться после инициализации. Активно используется в Redux для иммутабельности state.

Что запомнить

  • Object.preventExtensions() запрещает только добавление новых свойств.
  • Object.seal() запрещает добавление и удаление свойств, но разрешает изменение значений.
  • Object.freeze() запрещает любые изменения объекта на первом уровне.
  • Каждый следующий метод включает ограничения предыдущего.
  • Все три метода действуют поверхностно — вложенные объекты нужно замораживать отдельно.
  • Операции ограничения необратимы — «разморозить» объект нельзя.
  • В strict mode нарушения ограничений вызывают TypeError, в обычном режиме — молча игнорируются.
  • Проверить состояние объекта помогают методы Object.isExtensible(), Object.isSealed() и Object.isFrozen().