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

Как сделать поля в объекте неизменяемыми?

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

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

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

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

Как сделать поля в объекте неизменяемыми?

Создание неизменяемых (immutable) свойств объекта - это важный паттерн для предотвращения случайных или злонамеренных изменений данных. Существует несколько способов это реализовать.

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

Один из самых простых и прямых подходов - использовать Object.freeze():

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

// Замораживаем объект
Object.freeze(user);

// Попытка изменить свойство - не сработает
user.name = 'Bob'; // Молча не срабатывает (в strict mode выбросит ошибку)
console.log(user.name); // 'Alice' (не изменилось)

// Попытка добавить новое свойство
user.phone = '123-456'; // Также не сработает
console.log(user.phone); // undefined

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

Object.defineProperty() для отдельных свойств

Для более гибкого контроля используйте Object.defineProperty():

const person = {};

// Создаём неизменяемое свойство
Object.defineProperty(person, 'id', {
  value: 12345,
  writable: false,       // Не может быть переписано
  enumerable: true,      // Видно в for...in
  configurable: false    // Не может быть удалено
});

// Создаём свойство только для чтения с getter
Object.defineProperty(person, 'name', {
  get() {
    return this._name || 'Anonymous';
  },
  enumerable: true,
  configurable: false
});

console.log(person.id); // 12345
person.id = 999; // Не имеет эффекта
console.log(person.id); // 12345 (не изменилось)

Object.seal() для защиты структуры

Object.seal() позволяет менять существующие свойства, но не добавлять новые:

const config = {
  apiUrl: 'https://api.example.com',
  timeout: 5000,
  retries: 3
};

// Защищаем структуру объекта
Object.seal(config);

// Можем менять существующие свойства
config.timeout = 10000;
console.log(config.timeout); // 10000 (изменилось)

// Но не можем добавлять новые
config.debug = true; // Молча не срабатывает
console.log(config.debug); // undefined

// Проверка запечатан ли объект
console.log(Object.isSealed(config)); // true

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

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

function deepFreeze(obj) {
  // Замораживаем сам объект
  Object.freeze(obj);

  // Рекурсивно замораживаем все свойства
  Object.getOwnPropertyNames(obj).forEach(prop => {
    if (obj[prop] !== null
        && (typeof obj[prop] === 'object' || typeof obj[prop] === 'function')
        && !Object.isFrozen(obj[prop])) {
      deepFreeze(obj[prop]);
    }
  });

  return obj;
}

const deepConfig = {
  database: {
    host: 'localhost',
    port: 5432
  },
  cache: {
    redis: {
      enabled: true
    }
  }
};

deepFreeze(deepConfig);

// Попытка изменить вложенное значение
deepConfig.database.host = 'remote.server'; // Не срабатывает
console.log(deepConfig.database.host); // 'localhost'

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

Proxy даёт максимальный контроль над доступом к свойствам:

const user = {
  name: 'Alice',
  password: 'secret123'
};

const readOnlyProxy = new Proxy(user, {
  set(target, property, value) {
    // Запретить любые попытки записи
    throw new Error(`Cannot modify property "${property}"`);
  },
  deleteProperty(target, property) {
    throw new Error(`Cannot delete property "${property}"`);
  }
});

// Попытка изменения выбросит ошибку
try {
  readOnlyProxy.name = 'Bob'; // Выбросит ошибку
} catch (e) {
  console.log(e.message); // "Cannot modify property \"name\""
}

// Можем читать значения
console.log(readOnlyProxy.name); // 'Alice'

Proxy с избирательным контролем

const user = {
  id: 1,
  name: 'Alice',
  email: 'alice@example.com',
  password: 'secret'
};

const protectedProxy = new Proxy(user, {
  set(target, property, value) {
    // Защищённые свойства
    const protected = ['id', 'password'];
    if (protected.includes(property)) {
      throw new Error(`Cannot modify protected property "${property}"`);
    }
    
    // Валидация email
    if (property === 'email' && !value.includes('@')) {
      throw new Error('Invalid email format');
    }
    
    target[property] = value;
    return true;
  }
});

// Можем менять name и email (с валидацией)
protectedProxy.name = 'Bob';
protectedProxy.email = 'bob@example.com';
console.log(protectedProxy.name); // 'Bob'

// Но не можем менять id и password
try {
  protectedProxy.id = 2; // Выбросит ошибку
} catch (e) {
  console.log(e.message); // "Cannot modify protected property \"id\""
}

React компонент с неизменяемым состоянием

import { useState } from 'react';

function UserProfile() {
  const [user] = useState(() => {
    const userData = {
      id: 1,
      name: 'Alice',
      email: 'alice@example.com'
    };
    
    // Замораживаем начальное состояние
    return Object.freeze(userData);
  });

  // Попытка изменить приведёт к ошибке при strict mode
  // user.name = 'Bob'; // TypeError

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}

Сравнение подходов

const obj = { name: 'Alice', config: { theme: 'dark' } };

// 1. Object.freeze() - полная заморозка (поверхностная)
const frozen = Object.freeze(obj);
// frozen.name = 'Bob'; // Не работает
// frozen.config.theme = 'light'; // РАБОТАЕТ! (вложенные объекты не заморознуты)

// 2. Object.seal() - защита структуры
const sealed = Object.seal(obj);
// sealed.name = 'Bob'; // Работает
// sealed.newProp = 'value'; // Не работает

// 3. Object.defineProperty() - гибкий контроль
Object.defineProperty(obj, 'name', { writable: false });
// obj.name = 'Bob'; // Не работает

// 4. Proxy - максимальный контроль
const proxy = new Proxy(obj, { set: () => false });
// proxy.name = 'Bob'; // Не работает, но можно добавить пользовательскую логику

Best Practices

  • Используйте Object.freeze() для простых случаев
  • Применяйте deepFreeze() для сложных структур данных
  • Используйте Proxy когда нужна пользовательская логика контроля
  • Помните что freeze() работает поверхностно на массивы и объекты
  • В TypeScript используйте readonly типы для типизации
  • Тестируйте в strict mode чтобы поймать ошибки
  • Для больших объектов рассмотрите использование Immutable.js библиотеки