Property Descriptors

Property Descriptor — объект метаданных, описывающий поведение свойства объекта: можно ли его перезаписать (writable), перечислить (enumerable) и удалить или переконфигурировать (configurable).

Зачем нужно

Дескрипторы дают точный контроль над поведением свойств объектов: создание констант, скрытых свойств, вычисляемых геттеров/сеттеров. Понимание дескрипторов необходимо для реализации реактивности (Vue 2 Object.defineProperty), создания иммутабельных структур и построения надёжных библиотечных API.

Где используется

  • Создание неизменяемых свойств (константы в объекте)
  • Геттеры/сеттеры для вычисляемых свойств
  • Скрытие служебных свойств от for...in и Object.keys()
  • Реализация реактивности (Vue 2, MobX)
  • Защита объектов через Object.freeze / Object.seal

Основной контент

Чтение дескриптора

const obj = { x: 42 };
console.log(Object.getOwnPropertyDescriptor(obj, 'x'));
// {
//   value: 42,
//   writable: true,
//   enumerable: true,
//   configurable: true
// }

Object.defineProperty

const config = {};

Object.defineProperty(config, 'MAX_SIZE', {
  value: 100,
  writable: false,     // нельзя перезаписать
  enumerable: true,    // виден в for...in
  configurable: false  // нельзя удалить/переконфигурировать
});

config.MAX_SIZE = 200;           // тихо игнорируется (или TypeError в strict mode)
console.log(config.MAX_SIZE);    // 100

delete config.MAX_SIZE;          // тихо игнорируется
console.log(config.MAX_SIZE);    // 100

// Скрытое свойство (enumerable: false)
Object.defineProperty(config, '_internal', {
  value: 'secret',
  enumerable: false,
  writable: true,
  configurable: true
});

console.log(Object.keys(config));        // ['MAX_SIZE'] — _internal скрыто
console.log(config._internal);           // 'secret' — но доступно напрямую

Геттеры и сеттеры

const person = {
  _firstName: 'Иван',
  _lastName: 'Иванов'
};

Object.defineProperty(person, 'fullName', {
  get {
    return `${this._firstName} ${this._lastName}`;
  },
  set(value) {
    const [first, ...rest] = value.split(' ');
    this._firstName = first;
    this._lastName = rest.join(' ');
  },
  enumerable: true,
  configurable: true
});

console.log(person.fullName);    // 'Иван Иванов'
person.fullName = 'Пётр Петров';
console.log(person._firstName);  // 'Пётр'

// Геттер в литерале объекта (синтаксический сахар)
const circle = {
  radius: 5,
  get area { return Math.PI * this.radius ** 2; }
};
console.log(circle.area.toFixed(2)); // '78.54'

Object.defineProperties и заморозка

// Несколько свойств за раз
const point = {};
Object.defineProperties(point, {
  x: { value: 0, writable: true, enumerable: true, configurable: true },
  y: { value: 0, writable: true, enumerable: true, configurable: true }
});

// Object.freeze = все writable: false, configurable: false (поверхностно)
const COLORS = Object.freeze({ RED: '#ff0000', GREEN: '#00ff00' });
COLORS.RED = '#000'; // тихо игнорируется
console.log(COLORS.RED); // '#ff0000'

Частые ошибки

  • Тихое игнорирование в non-strict mode — запись в writable: false в обычном режиме просто игнорируется без ошибки. В 'use strict' выбрасывается TypeError. Это маскирует баги.
  • Object.freeze — только поверхностная заморозка — вложенные объекты не замораживаются. Для глубокой заморозки нужна рекурсивная функция.
  • Смешение value и get/set — дескриптор не может одновременно содержать value/writable и get/set. Это TypeError.

Связанные темы

Ресурсы


⚡ Источник: Что такое Object в JavaScript согласно спецификации · AsForJS

  • 📅 2025-02-03 · YouTube
  • Тезисы: Property Descriptor — это конкретная Specification Type. У всякого свойства внутри объекта есть либо Data Descriptor (value, writable), либо Accessor Descriptor (get, set). Любое присваивание obj.x = 5 под капотом — это [[DefineOwnProperty]] с дескриптором