Дескрипторы свойств объекта в JavaScript

Понятие дескриптора свойства объекта

Объект и его свойства (поля)

Объект — это набор свойств и их значений в памяти, на которые можно сослаться с помощью идентификатора.

Свойство объекта – это пара «ключ: значение» (или key: value):

  • ключ — это идентификатор (или имя) свойства (тип String или Symbol),
  • значения могут быть любыми (любой тип, включая другие объекты).

Свойства объекта также иногда называют полями объекта.

Значения свойств могут иметь любой тип, включая другие объекты, что позволяет строить сложные, разветвлённые иерархии данных.

[свернуть]

Дескриптор (флаг) — это объект, позволяющий описать поведение свойства при выполнении определённых операций над ним (например, чтения или записи).

Типы дескрипторов свойств объектов:

  1. дескрипторы данных (для свойства, имеющего значение, которое может (или не может) быть записываемым);
  2. дескрипторы доступа (свойство, описываемое парой функций — геттером (get) и сеттером (set).

Дескриптор может быть только одним из этих двух типов; он не может быть одновременно обоими.

Дескрипторы свойств (как объекты) могут содержать следующие ключи:

  1. value — значение, ассоциированное со свойством; может быть любым допустимым значением JavaScript (числом, объектом, функцией и т.д.);
  2. writable — если true , то значение, ассоциированное со свойством, может быть изменено с помощью оператора присваивания;
  3. enumerable — если true, то это свойство доступно через перечисление свойств содержащего его объекта (при false свойство будет отсутствовать в перечне перечисляемых свойств объекта);
  4. configurable — если true, то тип свойства может быть изменен и если свойство может быть удалено из содержащего его объекта;
  5. get — функция, используемая как геттер свойства (undefined, если свойство не имеет геттера); возвращаемое значение функции будет использоваться как значение свойства;
  6. set — функция, используемая как сеттер свойства (undefined, если свойство не имеет сеттера); функция принимает единственным аргументом новое значение, присваиваемое свойству.
Дескриптор Значение по умолчанию  
Value undefined  
Get undefined  
Set undefined  
Writable false Возможность изменения значения [[Value]]
Enumerable false Если true, свойство будет перечислено в циклах for … in.
Configurable false Если false, свойство не может быть удалено, не может быть изменено на свойство-аксессор (get/set), а атрибуты, отличные от [[Value]] и [[Writable]], не могут быть изменены.

ВАЖНО! Чтобы избежать конфликтов запрещено одновременно указывать значение value и функции get/set  (указывают либо значение (value), либо функции для его чтения-записи (get или set)). Также запрещено и не имеет смысла указывать writable при наличии get/set-функций.

Замечание по значениям дескрипторов

Значения дескрипторов по умолчанию зависят от способа добавления свойств:

[свернуть]
Примеры использования дескрипторов

Дескриптор Writable:

Дескриптор Configurable:

Дескриптор configurable определяет:

  1. можно ли изменить тип свойства enumerable или configurable с помощью функции Object.defineProperty();
  2. может ли свойство  быть удалено из содержащего его объекта.

Значение дескриптора value будет перезаписано только в том случае, если значения для остальных дескрипторов полностью совпадают с предыдущим присваиванием:

Дескриптор Enumerable:

Дескриптор enumerable определяет:

  1. будет ли свойство перебираться с помощью цикла for .. in;
  2. можно ли получить название свойства с помощью функции Object.keys.

[свернуть]

Использование дескрипторов при работе со свойствами объекта

Получение значений дескрипторов для свойств объекта:

Получение дескрипторов одного свойства ( метод Object.getOwnPropertyDescriptor() )

Метод Object.getOwnPropertyDescriptor() возвращает дескриптор собственного свойства  объекта, переданного в метод (собственное свойство объекта находится непосредственно в объекте, а не получено через цепочку прототипов.

где obj — объект, в котором ищется свойство; prop — имя свойства, чьё описание (дескрипторы) будет возвращено.

Метод Object.getOwnPropertyDescriptor()  возвращает дескриптор переданного свойства, если оно присутствует в объекте, либо undefined, если его там нет.

[свернуть]
Получение дескрипторов всех свойств объекта ( метод Object.getOwnPropertyDescriptors() )

Метод Object.getOwnPropertyDescriptors() возвращает все собственные дескрипторы свойств переданного объекта.

где obj — объект, для которого нужно получить все собственные дескрипторы свойств.

Метод Object.getOwnPropertyDescriptors() возвращает объект, содержащий все собственные дескрипторы свойств объекта. Если у переданного объекта нет свойств, то возвращается пустой объект.

[свернуть]
Примеры

[свернуть]

Добавление (изменение) свойств объекта:

Добавление (изменение) одного свойства объекта ( метод Object.defineProperty() )

Метод Object.defineProperties() определяет (добавляет) новые или изменяет существующие свойства, непосредственно в объекте, возвращая этот объект.

При вызове метода учитывается значение дескриптора configurable!

где obj — объект, в котором определяются новые или изменяются существующие свойства; props — объект, чьи собственные перечисляемые свойства представляют собой дескрипторы для создаваемых или изменяемых свойств.

Метод возвращает измененный объект.

Обратите внимание на значения по умолчанию дескрипторов configurable: false и  writable: false.

[свернуть]

Добавление (изменение) нескольких свойств объекта ( метод Object.defineProperties() )

Метод Object.defineProperties() позволяет определять все свойства переданного объекта.

При вызове метода учитывается значение дескриптора configurable!

[свернуть]

Геттеры (get) и сеттеры (set)

Дескрипторы get и set (более известные как геттеры и сеттеры) позволяют задавать функции в качестве свойств объекта, а также запускать их при запросе для получения или записи значения свойства (соответственно):

  • get – функция, которая возвращает значение свойства (по умолчанию undefined);
    set – функция, которая записывает значение свойства (по умолчанию undefined).

При этом мы не вызываем геттер (например, myObj.getter) или сеттер как функцию, а просто читаем его значение (геттер или сеттер выполняются скрыто). Снаружи свойство-аксессор выглядит как обычное свойство объекта.

Указание get и set в качестве свойства в литерале объекта

Если мы создаём объект при помощи литерального синтаксиса { … }, то задать свойства-функции можно прямо в его определении.

Геттер срабатывает, когда myCar.allInfo вызывается (читается), сеттер – когда значение myCar.age присваивается.

Добавление get или set в существующий объект

С помощью методов Object.defineProperty() и Object.defineProperties() можно добавить get или set в уже существующий объект.

Ещё раз отметим, что свойство объекта может быть либо свойством-данным (со значением value), либо свойством-аксессором (с методами get/set).

ВАЖНО! Попытка указать и get/set, и value в одном дескрипторе вызовет ошибку:

Использование get или set в качестве обёртки

Геттеры/сеттеры можно использовать как обёртки над «реальными» значениями свойств, чтобы получить больше контроля над операциями с ними.

Например, если мы хотим запретить устанавливать короткое имя для login, мы можем использовать сеттер login для проверки, а само значение хранить в отдельном внутреннем свойстве _login:

Таким образом, login хранится в свойстве _login, доступ к которому производится через геттер и сеттер.

Замечание: технически можно получить доступ к login напрямую с помощью userLogin._login, но существует соглашение о том что свойства, которые начинаются с символа «_», являются внутренними, и к ним не следует обращаться из-за пределов объекта.

Использование get или set для совместимости кода

Аксессоры позволяют в любой момент взять «обычное» свойство и изменить его поведение, поменяв на геттер и сеттер.

Пример отсюда

Например, представим, что мы начали реализовывать объект user, используя свойства-данные имя name и возраст age:

Но вместо возраста age мы можем решить хранить дату рождения birthday, потому что так более точно и удобно:

Что делать со старым кодом, который использует свойство age?

Мы можем попытаться найти все такие места и изменить их, но это отнимает время и может быть невыполнимо, если код используется другими людьми. И кроме того, age может использоваться и в других местах кода. Давайте его сохраним.

Добавление геттера для age решит проблему:

Теперь старый код тоже работает, и у нас есть дополнительное свойство age.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.