Дескрипторы свойств JavaScript
Флаги свойств JavaScript (writable, enumerable, configurable) и дескрипторы: data vs accessor, defineProperty, getOwnPropertyDescriptors, freeze, seal и строгий режим.
Флаги и дескрипторы свойств JavaScript обеспечивают точный контроль над свойствами объектов, позволяя создавать надёжные и защищённые приложения. В этой статье подробно рассмотрены эти возможности с практическими примерами, которые помогут эффективно управлять поведением свойств.
Атрибуты свойств JavaScript
JavaScript-объекты — это коллекции свойств, и у каждого свойства есть атрибуты, определяющие его поведение. Эти атрибуты, называемые флагами свойств, включают:
- Writable: определяет, можно ли изменить значение свойства.
- Enumerable: управляет видимостью свойства при перечислении, например в цикле
for...in. - Configurable: указывает, можно ли удалить свойство или изменить его описание.
Эти флаги критически важны для управления доступом к свойствам объекта, обеспечения целостности данных и реализации инкапсуляции в JavaScript-приложениях.
Дескрипторы свойств
Дескрипторы свойств содержат подробную информацию о свойстве объекта: его значение и флаги. Для их получения используется Object.getOwnPropertyDescriptor(obj, propName), а для установки — Object.defineProperty(obj, propName, descriptor). Объект дескриптора может содержать:
value: значение, связанное со свойством.writable: указывает, можно ли изменить значение свойства.enumerable: указывает, является ли свойство перечисляемым.configurable: определяет, можно ли изменить дескриптор свойства и удалить свойство из объекта.
Примечание: при создании свойства обычным способом (user.name = "John") все три флага устанавливаются в true. Однако при определении нового свойства через Object.defineProperty любой неуказанный флаг по умолчанию принимает значение false.
Object.getOwnPropertyDescriptor проверяет только собственные свойства объекта. Если запросить свойство, унаследованное от прототипа (или вовсе отсутствующее), метод вернёт undefined.
Подробнее о наследовании свойств объектами см. в разделе Прототипное наследование.
Дескрипторы данных и дескрипторы доступа
До сих пор мы рассматривали дескрипторы данных, которые хранят value вместе с флагом writable. JavaScript также поддерживает дескрипторы доступа, в которых value/writable заменяются функциями-геттером и сеттером:
get: функция, вызываемая при чтении свойства (не принимает аргументов).set: функция, вызываемая при присваивании свойству (получает новое значение).
Дескриптор является либо дескриптором данных, либо дескриптором доступа — но не тем и другим одновременно. Сочетание value/writable с get/set вызывает ошибку. Оба вида дескрипторов разделяют флаги enumerable и configurable.
Свойства-аксессоры позволяют вычислять значение при чтении или проверять его при записи. Ниже показано определение аксессора fullName, основанного на двух свойствах данных:
Полное описание синтаксиса get/set (включая сокращённую форму внутри литералов объектов) см. в разделе Геттеры и сеттеры свойств. Поскольку геттеры и сеттеры вызываются с this, привязанным к объекту, полезно также ознакомиться с разделом Методы объекта и «this».
Определение и чтение нескольких свойств за один шаг
Для работы с несколькими свойствами одновременно JavaScript предоставляет методы, работающие с коллекциями:
Object.defineProperties(obj, descriptors)определяет несколько свойств из набора дескрипторов.Object.getOwnPropertyDescriptors(obj)возвращает дескрипторы всех собственных свойств (включая неперечисляемые и символьные ключи) в виде единого объекта.
Object.getOwnPropertyDescriptors особенно полезен при клонировании объекта вместе с его флагами — обычный spread-оператор или Object.assign копирует значения, но сбрасывает все флаги в true и пропускает аксессоры.
Управление флагами свойств
Понимание флагов свойств и умение ими управлять — ключ к эффективной разработке на JavaScript. Рассмотрим, как контролировать эти флаги для тонкой настройки поведения свойств.
Запрет изменения свойства
Запрет на изменение свойства обеспечивает согласованность данных. Для этого нужно установить флаг writable в false.
Поведение при неудачном присваивании зависит от режима выполнения кода. В нестрогом режиме запись в свойство с writable: false завершается молча: присваивание просто игнорируется, ошибка не выбрасывается, выполнение продолжается — что может скрывать ошибки. В строгом режиме ("use strict", а также по умолчанию внутри ES-модулей и тел классов) то же самое присваивание выбрасывает TypeError. Это правило применяется к любой операции, нарушающей флаг: удаление неконфигурируемого свойства или добавление свойства к нерасширяемому объекту также завершается молча в нестрогом режиме и выбрасывает ошибку в строгом.
Скрытие свойства от перечисления
Иногда необходимо скрыть свойства от процессов перечисления, например от цикла for...in. Для этого нужно установить флаг enumerable в false.
Запрет удаления и изменения свойства
Чтобы свойство оставалось неизменной частью объекта, установите флаг configurable в false.
Установка свойства как неконфигурируемого — необратимая операция: вернуть ему конфигурируемость невозможно, и уже нельзя переключить enumerable или изменить тип дескриптора с data на accessor.
Тем не менее существуют два важных исключения для неконфигурируемого свойства:
- Можно изменить
writableсtrueнаfalse(но не обратно). - Если свойство по-прежнему имеет
writable: true, можно изменить егоvalue— как прямым присваиванием, так и черезObject.defineProperty.
Иными словами, configurable: false фиксирует форму свойства, но не обязательно его значение. Чтобы полностью заморозить значение свойства, установите одновременно configurable: false и writable: false.
Высокоуровневые API на основе флагов
Устанавливать флаги по одному свойству за раз приходится редко. JavaScript предоставляет три встроенных метода, которые изменяют флаги сразу для всего объекта:
Object.preventExtensions(obj)— запрещает добавление новых свойств. Существующие свойства по-прежнему можно изменять и удалять.Object.seal(obj)— запрещает добавление и удаление свойств, устанавливая каждому существующему свойствуconfigurable: false. Значения ещё можно менять.Object.freeze(obj)— самый строгий вариант: запечатывает объект и делает каждое свойствоwritable: false, поэтому ничего нельзя добавить, удалить или изменить.
Для каждого метода есть соответствующая проверка: Object.isExtensible, Object.isSealed и Object.isFrozen. Обратите внимание, что эти операции работают только на одном уровне глубины — Object.freeze не замораживает вложенные объекты (это «поверхностная» заморозка).
Заключение
Флаги и дескрипторы свойств дают точный контроль над поведением свойств объектов:
- Дескриптор данных объединяет
valueсwritable; дескриптор доступа использует вместо них функцииget/set. Оба вида разделяют флагиenumerableиconfigurable. - Читать флаги можно с помощью
Object.getOwnPropertyDescriptor(одно свойство) илиObject.getOwnPropertyDescriptors(все собственные свойства); устанавливать — черезObject.definePropertyилиObject.defineProperties. Для унаследованных и отсутствующих свойств возвращаетсяundefined. configurable: false— необратимая операция, фиксирующая форму свойства, хотя у свойства сwritable: trueзначение всё ещё можно изменить, а сам флагwritableможно сбросить вfalse.- Нарушение флага в нестрогом режиме завершается молча, а в строгом режиме выбрасывает
TypeError. - Используйте
Object.freeze,Object.sealиObject.preventExtensions, когда нужно заблокировать весь объект, а не отдельные флаги.
Следующие шаги: изучите Геттеры и сеттеры свойств для синтаксиса аксессоров, Методы объекта и «this» — о том, как ведёт себя this внутри них, и Прототипное наследование — о том, как поиск свойств выполняется по цепочке прототипов.