Как мы знаем, объекты могут хранить свойства.
До сих пор для нас свойство представляло собой простую пару «ключ-значение». Но свойство объекта на самом деле является более гибкой и мощной вещью.
В этой главе мы изучим дополнительные параметры конфигурации, а в следующей увидим, как незаметно превратить их в функции получения/установки.
Свойства объекта, кроме value
, имеют три специальных атрибута (так называемые «флаги»):
writable
— если true
, значение можно изменить, в противном случае оно доступно только для чтения.
enumerable
– если true
, то перечисляется в циклах, иначе не перечисляется.
configurable
— если true
, свойство можно удалить и эти атрибуты можно изменить, в противном случае — нет.
Мы их еще не видели, потому что обычно они не появляются. Когда мы создаем свойство «обычным способом», все они true
. Но мы также можем изменить их в любое время.
Во-первых, давайте посмотрим, как получить эти флаги.
Метод Object.getOwnPropertyDescriptor позволяет запросить полную информацию о свойстве.
Синтаксис:
пусть дескриптор = Object.getOwnPropertyDescriptor(obj, propertyName);
obj
Объект, от которого нужно получить информацию.
propertyName
Название объекта недвижимости.
Возвращаемое значение представляет собой так называемый объект «дескриптор свойства»: он содержит значение и все флаги.
Например:
пусть пользователь = { имя: «Джон» }; пусть дескриптор = Object.getOwnPropertyDescriptor(пользователь, 'имя'); alert( JSON.stringify(дескриптор, ноль, 2)); /* дескриптор свойства: { "значение": "Джон", «записываемый»: правда, «исчисляемый»: правда, «настраиваемый»: правда } */
Чтобы изменить флаги, мы можем использовать Object.defineProperty.
Синтаксис:
Object.defineProperty(obj, имя_свойства, дескриптор)
obj
, propertyName
Объект и его свойство, к которому применяется дескриптор.
descriptor
Объект дескриптора свойства, который необходимо применить.
Если свойство существует, defineProperty
обновляет его флаги. В противном случае создается свойство с заданным значением и флагами; в этом случае, если флаг не указан, он считается false
.
Например, здесь name
свойства создается со всеми ложными флагами:
пусть пользователь = {}; Object.defineProperty(пользователь, "имя", { значение: «Джон» }); пусть дескриптор = Object.getOwnPropertyDescriptor(пользователь, 'имя'); alert( JSON.stringify(дескриптор, ноль, 2)); /* { "значение": "Джон", «доступно для записи»: ложь, «перечислимый»: ложь, «настраиваемый»: ложь } */
Сравните его с «обычно созданным» user.name
выше: теперь все флаги ложны. Если это не то, что нам нужно, нам лучше установить для них значение true
в descriptor
.
Теперь давайте посмотрим на эффекты флагов на примере.
Давайте сделаем user.name
недоступным для записи (нельзя переназначить), изменив флаг writable
:
пусть пользователь = { имя: «Джон» }; Object.defineProperty(пользователь, "имя", { записываемый: ложь }); user.name = "Пит"; // Ошибка: невозможно назначить свойство «имя» только для чтения
Теперь никто не сможет изменить имя нашего пользователя, если только он не применит свой собственный defineProperty
для переопределения нашего.
Ошибки появляются только в строгом режиме
В нестрогом режиме не возникает ошибок при записи в недоступные для записи свойства и тому подобное. Но операция все равно не увенчается успехом. Действия, нарушающие флаг, просто молча игнорируются в нестрогом режиме.
Вот тот же пример, но свойство создается с нуля:
пусть пользователь = {}; Object.defineProperty(пользователь, "имя", { значение: «Джон», // для новых свойств нам нужно явно указать, что истинно перечислимое: правда, настраиваемый: правда }); оповещение(имя_пользователя); // Джон user.name = "Пит"; // Ошибка
Теперь давайте добавим пользовательскую toString
к user
.
Обычно встроенная toString
для объектов неперечислима и не отображается в for..in
. Но если мы добавим собственную toString
, то по умолчанию она появится в for..in
, вот так:
пусть пользователь = { имя: «Джон», toString() { вернуть это.имя; } }; // По умолчанию указаны оба наших свойства: for (введите пользователя) alert(key); // имя, toString
Если нам это не нравится, мы можем установить enumerable:false
. Тогда он не появится в цикле for..in
, как встроенный:
пусть пользователь = { имя: «Джон», toString() { вернуть это.имя; } }; Object.defineProperty(user, "toString", { перечисляемое: ложь }); // Теперь наша toString исчезает: for (введите пользователя) alert(key); // имя
Неперечислимые свойства также исключены из Object.keys
:
предупреждение(Object.keys(пользователь)); // имя
Ненастраиваемый флаг ( configurable:false
) иногда устанавливается для встроенных объектов и свойств.
Неконфигурируемое свойство нельзя удалить, его атрибуты нельзя изменить.
Например, Math.PI
не доступен для записи, неперечислим и не настраивается:
пусть дескриптор = Object.getOwnPropertyDescriptor(Math, 'PI'); alert( JSON.stringify(дескриптор, ноль, 2)); /* { «значение»: 3,141592653589793, «доступно для записи»: ложь, «перечислимый»: ложь, «настраиваемый»: ложь } */
Таким образом, программист не может изменить значение Math.PI
или перезаписать его.
Мат.ПИ = 3; // Ошибка, поскольку доступно для записи: false //удалить Math.PI тоже не получится
Мы также не можем снова сделать Math.PI
доступным для writable
:
// Ошибка из-за настраиваемого: false Object.defineProperty(Math, "PI", {доступно для записи: true });
С Math.PI
мы абсолютно ничего не можем сделать.
Сделать свойство ненастраиваемым — это дорога с односторонним движением. Мы не можем изменить его обратно с помощью defineProperty
.
Обратите внимание: configurable: false
предотвращает изменение флагов свойства и его удаление, но позволяет изменить его значение.
Здесь user.name
не настраивается, но мы все равно можем его изменить (поскольку оно доступно для записи):
пусть пользователь = { имя: «Джон» }; Object.defineProperty(пользователь, "имя", { настраиваемый: ложь }); user.name = "Пит"; // работает нормально удалить имя пользователя; // Ошибка
И здесь мы делаем user.name
«навсегда запечатанной» константой, точно так же, как встроенный Math.PI
:
пусть пользователь = { имя: «Джон» }; Object.defineProperty(пользователь, "имя", { записываемый: ложь, настраиваемый: ложь }); // не сможем изменить user.name или его флаги // все это не сработает: user.name = "Пит"; удалить имя пользователя; Object.defineProperty(user, "name", { value: "Pete" });
Единственное возможное изменение атрибута: доступно для записи true → false
Есть небольшое исключение относительно смены флагов.
Мы можем изменить writable: true
на false
для ненастраиваемого свойства, тем самым предотвращая изменение его значения (чтобы добавить еще один уровень защиты). Но не наоборот.
Существует метод Object.defineProperties(obj, descriptors), который позволяет определять множество свойств одновременно.
Синтаксис:
Object.defineProperties(obj, { реквизит1: дескриптор1, реквизит2: дескриптор2 // ... });
Например:
Object.defineProperties(пользователь, { имя: {значение: "Джон", возможность записи: false }, фамилия: {значение: "Смит", возможность записи: false }, // ... });
Таким образом, мы можем установить множество свойств одновременно.
Чтобы получить все дескрипторы свойств одновременно, мы можем использовать метод Object.getOwnPropertyDescriptors(obj).
Вместе с Object.defineProperties
его можно использовать как способ клонирования объекта с учетом флагов:
let clone = Object.defineProperties({}, Object.getOwnPropertyDescriptors(obj));
Обычно, когда мы клонируем объект, мы используем присваивание для копирования свойств, например:
for (введите пользователя) { клон[ключ] = пользователь[ключ] }
…Но это не копирует флаги. Поэтому, если нам нужен «лучший» клон, предпочтительным является Object.defineProperties
.
Другое отличие состоит в том, что for..in
игнорирует символические и неперечисляемые свойства, а Object.getOwnPropertyDescriptors
возвращает все дескрипторы свойств, включая символические и неперечисляемые.
Дескрипторы свойств работают на уровне отдельных свойств.
Существуют также методы, ограничивающие доступ ко всему объекту:
Object.preventExtensions(obj)
Запрещает добавление к объекту новых свойств.
Object.seal(obj)
Запрещает добавление/удаление свойств. Устанавливает configurable: false
для всех существующих свойств.
Object.freeze(obj)
Запрещает добавление/удаление/изменение свойств. Устанавливает configurable: false, writable: false
для всех существующих свойств.
А также для них есть тесты:
Object.isExtensible(obj)
Возвращает false
если добавление свойств запрещено, в противном случае — true
.
Object.isSealed(obj)
Возвращает true
, если добавление/удаление свойств запрещено, а все существующие свойства имеют configurable: false
.
Object.isFrozen(obj)
Возвращает true
если добавление/удаление/изменение свойств запрещено, и все текущие свойства доступны configurable: false, writable: false
.
Эти методы редко используются на практике.