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

Как запретить изменение через дескриптор?

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

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

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

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

Защита объектов: дескрипторы и Object.freeze

В JavaScript есть несколько способов запретить изменение свойств объекта. Это полезно для защиты критичных данных и обеспечения целостности конфигурации.

Property Descriptors (Object.defineProperty)

Каждое свойство объекта имеет дескриптор, определяющий его поведение. Самые важные флаги:

  • writable — можно ли изменять значение
  • configurable — можно ли удалить или переопределить дескриптор
  • enumerable — видно ли в for...in и Object.keys()
const config = {};

// Создаём неизменяемое свойство
Object.defineProperty(config, 'apiUrl', {
  value: 'https://api.example.com',
  writable: false,      // Нельзя изменять
  configurable: false,  // Нельзя переопределить
  enumerable: true      // Видно в перечислении
});

console.log(config.apiUrl); // 'https://api.example.com'

// Попытка изменить
config.apiUrl = 'https://other.com';
console.log(config.apiUrl); // 'https://api.example.com' (не изменилось)

// В strict mode выбросится ошибка
'use strict';
config.apiUrl = 'https://other.com'; // TypeError: Cannot assign to read only property

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

Object.freeze() делает объект полностью неизменяемым на поверхностном уровне:

const user = {
  name: 'John',
  age: 30,
  email: 'john@example.com'
};

Object.freeze(user);

// Изменение не сработает
user.name = 'Jane';  // Игнорируется
user.age = 31;       // Игнорируется
delete user.email;   // Игнорируется

// В strict mode выбросится TypeError
'use strict';
user.name = 'Jane'; // TypeError: Cannot assign to read only property 'name'

Object.seal (запрет добавления и удаления)

Чуть менее строгий вариант — разрешает изменение существующих свойств, но запрещает добавление и удаление:

const product = { id: 1, name: 'Laptop', price: 999 };

Object.seal(product);

// Изменение существующих свойств — ОК
product.price = 899;  // Работает

// Добавление новых свойств — запрещено
product.inStock = true;  // Игнорируется

// Удаление свойств — запрещено
delete product.name;  // Игнорируется

Глубокая заморозка (Deep Freeze)

Object.freeze() работает только на поверхностном уровне. Вложенные объекты остаются изменяемыми:

const app = Object.freeze({
  version: '1.0',
  settings: {
    theme: 'dark',
    language: 'en'
  }
});

// Поверхностный уровень заморожен
app.version = '2.0';  // Не сработает

// Но вложенные объекты нет!
app.settings.theme = 'light';  // Сработает!
console.log(app.settings.theme); // 'light'

Для полной заморозки нужна рекурсивная функция:

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

const config = deepFreeze({
  api: {
    baseUrl: 'https://api.example.com',
    timeout: 5000,
    endpoints: {
      users: '/users',
      posts: '/posts'
    }
  }
});

// Теперь полностью защищено на любом уровне
config.api.endpoints.users = '/v2/users'; // Не сработает

Использование Proxy для более гибкого контроля

Для наиболее точного контроля можешь использовать Proxy:

const handler = {
  set(target, property, value) {
    console.warn(`Попытка изменить ${property}`);
    return false;  // Блокируем изменение
  },
  
  deleteProperty(target, property) {
    console.warn(`Попытка удалить ${property}`);
    return false;  // Блокируем удаление
  }
};

const config = new Proxy({
  apiUrl: 'https://api.example.com',
  apiKey: 'secret123'
}, handler);

config.apiUrl = 'https://other.com';
// Вывод: Попытка изменить apiUrl

delete config.apiKey;
// Вывод: Попытка удалить apiKey

Сравнение методов

Object.defineProperty (writable: false):
  - Контроль на уровне одного свойства
  - Поддержка дополнительных флагов (configurable, enumerable)
  - Более трудозатратно для больших объектов

Object.freeze:
  - Простота использования
  - Работает на поверхностном уровне
  - Для глубокой заморозки нужна рекурсия

Object.seal:
  - Позволяет изменять существующие свойства
  - Запрещает добавление и удаление
  - Компромисс между гибкостью и безопасностью

Proxy:
  - Максимальный контроль
  - Можно логировать попытки изменения
  - Небольшое снижение производительности

Вывод: для защиты конфигурации используй Object.freeze(), для критичных данных — deepFreeze(), а для fine-grained контроля — Object.defineProperty() или Proxy.