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

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

1.0 Junior🔥 121 комментариев
#JavaScript Core

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

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

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

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

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

Способ 1: Object.defineProperty()

Это основной способ контролировать поведение свойства через дескрипторы свойств.

const user = { name: 'John' }

// Сделаем свойство name неизменяемым
Object.defineProperty(user, 'name', {
  value: 'John',
  writable: false  // НЕЛЬЗЯ изменять
})

user.name = 'Jane'  // Ошибка в strict mode, игнорируется в обычном режиме
console.log(user.name)  // 'John' — не изменился

Дескрипторы свойств:

const config = { url: 'localhost' }

Object.defineProperty(config, 'url', {
  value: 'localhost',
  writable: false,      // Нельзя изменять значение
  enumerable: true,     // Видно в for...in и Object.keys()
  configurable: false   // Нельзя удалять или переопределять
})

config.url = 'example.com'  // Не сработает
delete config.url  // Не сработает (если configurable: false)
Object.defineProperty(config, 'url', { value: 'new' })  // Ошибка

Способ 2: Object.freeze()

Умораживает объект — нельзя добавлять, удалять или изменять свойства.

const user = {
  name: 'John',
  age: 30
}

Object.freeze(user)  // Объект заморозился

user.name = 'Jane'  // Не работает
user.country = 'USA'  // Не работает (добавление)
delete user.age  // Не работает (удаление)

console.log(user)  // { name: 'John', age: 30 } — неизменен

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

const obj = { x: 1 }
Object.freeze(obj)

console.log(Object.isFrozen(obj))  // true

Внимание: freeze поверхностный (shallow)!

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

Object.freeze(user)

user.name = 'Jane'  // Не работает
user.address.city = 'LA'  // РАБОТАЕТ! (вложенный объект не заморожен)

console.log(user)  // { name: 'John', address: { city: 'LA' } }

Глубокая заморозка (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: 'John',
  address: { city: 'NYC', zip: 10001 }
}

deepFreeze(user)

user.address.city = 'LA'  // Теперь не работает!
console.log(user)  // { name: 'John', address: { city: 'NYC', zip: 10001 } }

Способ 3: Object.seal()

Способ 3: Object.seal()

Запечатывает объект — можно изменять существующие свойства, но НЕЛЬЗЯ добавлять или удалять.

const user = { name: 'John', age: 30 }

Object.seal(user)

user.name = 'Jane'  // РАБОТАЕТ (изменение существующего свойства)
user.country = 'USA'  // Не работает (добавление нового)
delete user.age  // Не работает (удаление)

console.log(user)  // { name: 'Jane', age: 30 }

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

Способ 4: Свойства с getter/setter

Создаешь свойство, которое контролируется функциями.

const user = {
  _name: 'John',  // Приватное свойство (соглашение)
  
  // getter
  get name() {
    return this._name
  },
  
  // setter
  set name(value) {
    if (value.length < 2) {
      throw new Error('Имя должно быть минимум 2 символа')
    }
    this._name = value
  }
}

console.log(user.name)  // 'John'
user.name = 'Jane'  // Работает
user.name = 'A'  // Ошибка: имя слишком короткое

Со класс:

class User {
  constructor(name) {
    this._name = name
  }
  
  get name() {
    return this._name
  }
  
  set name(value) {
    if (value.length < 2) throw new Error('Too short')
    this._name = value
  }
}

const user = new User('John')
console.log(user.name)  // 'John'
user.name = 'Jane'  // OK

Способ 5: Object.preventExtensions()

Нельзя добавлять новые свойства, но можно изменять и удалять существующие.

const config = { url: 'localhost' }

Object.preventExtensions(config)

config.url = 'example.com'  // РАБОТАЕТ (изменение)
config.port = 3000  // Не работает (добавление)
delete config.url  // РАБОТАЕТ (удаление)

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

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

МетодИзменитьДобавитьУдалитьУровень
freeze()Shallow
seal()Shallow
preventExtensions()Shallow
Object.defineProperty() writable: falseТочечно
getter/setterКонтролируемоКонтролируемоКонтролируемоТочечно

Практические примеры

1. API конфигурация (должна быть неизменяема)

const API_CONFIG = Object.freeze({
  baseURL: 'https://api.example.com',
  timeout: 5000,
  headers: Object.freeze({
    'Content-Type': 'application/json'
  })
})

// Кто-то случайно не сломает конфиг
API_CONFIG.baseURL = 'https://evil.com'  // Не сработает
API_CONFIG.newProperty = 'hack'  // Не сработает

2. Константы в приложении

const ROLES = Object.freeze({
  ADMIN: 'admin',
  USER: 'user',
  GUEST: 'guest'
})

// ROLES.ADMIN = 'superadmin'  // Ошибка — защищено

3. Банковский счет (контролируемые изменения)

class BankAccount {
  constructor(initialBalance) {
    this._balance = initialBalance
  }
  
  get balance() {
    return this._balance
  }
  
  deposit(amount) {
    if (amount <= 0) throw new Error('Amount must be positive')
    this._balance += amount
  }
  
  withdraw(amount) {
    if (amount > this._balance) throw new Error('Insufficient funds')
    this._balance -= amount
  }
}

const account = new BankAccount(1000)
console.log(account.balance)  // 1000
account.deposit(500)  // OK
account.balance = 99999  // Ошибка — нельзя прямо менять
console.log(account.balance)  // 1500

4. React state (неизменяемость для оптимизации)

const user = Object.freeze({ name: 'John', age: 30 })

// Для изменения создаешь новый объект
const updatedUser = Object.freeze({ ...user, age: 31 })

// React видит разные объекты и обновляет
setUser(updatedUser)

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

Замораживание стоит времени, но затем операции быстрее:

const obj = { a: 1, b: 2, c: 3 }

// Замораживаем один раз
Object.freeze(obj)

// Теперь доступ быстрее (движок может оптимизировать)
for (let i = 0; i < 1000000; i++) {
  const x = obj.a  // Быстро
}

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

Используй freeze:

  • Для констант и конфигураций
  • Для API ответов, которые не должны меняться
  • Для параметров функции

Используй seal:

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

Используй getter/setter:

  • Для валидации
  • Для контролируемого доступа
  • Для вычисляемых свойств

НЕ используй:

  • Для производительности (перед freeze лучше использовать const)
  • Вместо правильной архитектуры

Модный способ: Proxy

Для более сложного контроля используй Proxy:

const user = { name: 'John' }

const handler = {
  set(target, property, value) {
    if (property === 'name') {
      throw new Error('Name is immutable')
    }
    target[property] = value
  }
}

const protectedUser = new Proxy(user, handler)

protectedUser.name = 'Jane'  // Ошибка: Name is immutable

Вывод

  1. Object.freeze() — для полной неизменяемости (shallow)
  2. Object.seal() — для защиты от добавления/удаления свойств
  3. Object.defineProperty() — для точечного контроля
  4. getter/setter — для валидации и контроля
  5. Proxy — для сложной логики

Выбирай в зависимости от задачи. Чаще всего достаточно freeze или seal.