Кратко
СкопированоОбъекты, как мы знаем, содержат свойства. У каждого из свойств объекта, кроме значения, есть ещё три флага конфигурации, которые могут принимать значения true или false. Эти флаги называются дескрипторами:
writable— доступно ли свойство для записи;enumerable— является ли свойство видимым при перечислениях (например, в циклеfor);. . in configurable— доступно ли свойство для переконфигурирования.
Когда мы создаём свойство объекта «обычным способом», эти три флага устанавливаются в значение true.
Для изменения значений дескрипторов применяется статический метод Object, а для чтения значений — Object.
Другими словами, дескрипторы — это пары ключ-значение, которые описывают поведение свойства объекта при выполнении операций над ним (например, чтения или записи).

Пример
СкопированоСоздадим объект и добавим в него свойство ОС для ноутбука. Сделаем это с помощью дескрипторов и статического метода Object.
Передаём в метод:
- объект, которому добавляем свойство;
- название свойства строкой;
- объект со значениями дескрипторов и ключом
value, содержащим значение свойства.
const laptop = {}Object.defineProperty(laptop, 'os', { value: 'MacOS', writable: false, enumerable: true, configurable: true})
const laptop = {}
Object.defineProperty(laptop, 'os', {
value: 'MacOS',
writable: false,
enumerable: true,
configurable: true
})
Свойство os будет недоступно для перезаписи, но будет видно при перечислении и доступно для переконфигурирования.
Попробуем перезаписать свойство os и выведем полученный результат:
laptop.os = 'Windows'console.log(laptop)// { 'os': 'MacOS' }
laptop.os = 'Windows'
console.log(laptop)
// { 'os': 'MacOS' }
Как пишется
Скопировано
Object.defineProperty(объект, имяСвойства, дескрипторы)
Object.defineProperty(объект, имяСвойства, дескрипторы)
Функция принимает следующие параметры:
объект— объект, свойство которого изменяем или добавляем;имяСвойства— свойство, для которого нужно применить дескриптор;дескриптор— дескриптор, описывающий поведение свойства.
Если свойство уже существует, Object обновит флаги.
Если свойство не существует, метод создаёт новое свойство с указанным значением и флагами. Если какой-либо флаг не указан явно, ему присваивается значение false.
Как понять
СкопированоДескрипторы, которые мы можем передать в Object, бывают двух типов — дескриптор данных и дескриптор доступа. Каждый тип дескриптора имеет свой набор свойств.
В обоих типах можно использовать общие свойства configurable и enumerable.
Дескриптор, передаваемый в Object может быть только одним типом дескриптора. Он не может быть одновременно обоими! Если передать в Object объект, содержащий и свойства дескриптора данных, и свойства дескриптора доступа, то метод выбросит ошибку Invalid property descriptor. Cannot both specify accessors and a value or writable attribute.
Дескриптор данных
СкопированоДескриптор данных — это дескриптор, который определяет значение свойства и возможность изменить это значение.
value— значение свойства, по умолчаниюundefined.writable— можно ли изменить значение с помощью оператора присваивания.
value
СкопированоСвойство value дескриптора данных отвечает за значение свойства объекта.
Добавим ноутбуку свойство «Размер экрана»:
Object.defineProperty(laptop, 'displaySize', { value: '15'})
Object.defineProperty(laptop, 'displaySize', {
value: '15'
})
Выведем полученные данные:
const descriptor = Object.getOwnPropertyDescriptor(laptop, 'displaySize')console.log(descriptor)
const descriptor = Object.getOwnPropertyDescriptor(laptop, 'displaySize')
console.log(descriptor)
Мы не указали остальные свойства явно, поэтому дескриптор имеет следующие значения:
{ "value": "15", "writable": false, "enumerable": false, "configurable": false}
{
"value": "15",
"writable": false,
"enumerable": false,
"configurable": false
}
writable
СкопированоСвойство writable дескриптора определяет, можно ли изменить значение свойства с помощью оператора присваивания. По умолчанию устанавливается в false для свойств, созданных через Object и в true, если свойство добавлено через оператор ..
Изменим значение writable:
const laptop = {}Object.defineProperty(laptop, 'displaySize', { value: '15', // Не перезаписываемо! writable: false, configurable: true, enumerable: true})laptop.displaySize = '18'console.log(laptop.displaySize)// { 'displaySize': '15' }
const laptop = {}
Object.defineProperty(laptop, 'displaySize', {
value: '15',
// Не перезаписываемо!
writable: false,
configurable: true,
enumerable: true
})
laptop.displaySize = '18'
console.log(laptop.displaySize)
// { 'displaySize': '15' }
В строгом режиме мы получим ошибку Type, которая говорит о том, что мы не можем изменить неперезаписываемое свойство.
Дескриптор доступа
СкопированоДескриптор доступа — это дескриптор, который определяет работу свойства через функции чтения и записи свойства (геттера и сеттера).
get — функция, используемая для получения значения свойства, возвращает значение или undefined.
set — функция, используемая для установки значения свойства. Принимает единственным аргументом новое значение, присваиваемое свойству.
Сравним простой объект с полем name и объект с геттером name, созданным через Object:
const animal = { _hiddenName : 'Кот' }Object.defineProperty(animal, 'name', { get: function() { return this._hiddenName }})const animal2 = { name: 'И здесь тоже кот',}console.log(animal.name)// Котconsole.log(animal2.name)// И здесь тоже кот
const animal = { _hiddenName : 'Кот' }
Object.defineProperty(animal, 'name', {
get: function() { return this._hiddenName }
})
const animal2 = {
name: 'И здесь тоже кот',
}
console.log(animal.name)
// Кот
console.log(animal2.name)
// И здесь тоже кот
Оба объекта имеют одинаковое поведение. Стоит только сказать, что за свойством в первом случае стоит функция, которая вызывается автоматически. Достаточно написать animal.
Если нам понадобится изменить значение свойства name, мы выполним animal, ничего не произойдёт. Дело в том, что с ключом name не связана функция-сеттер, поэтому значение этому свойству установить невозможно.
Добавим сеттер:
const animal = { _hiddenName : 'Кот' }Object.defineProperty(animal, 'name', { get: function() { return this._hiddenName }, set: function(value){ this._hiddenName = value }})animal.name = 'Собака'console.log(animal.name)// Собака
const animal = { _hiddenName : 'Кот' }
Object.defineProperty(animal, 'name', {
get: function() { return this._hiddenName },
set: function(value){ this._hiddenName = value }
})
animal.name = 'Собака'
console.log(animal.name)
// Собака
По сути, мы можем регулировать возможность читать и получать значение свойства, как и в дескрипторе данных, только более тонко. Такой подход используется часто, поэтому для объявления геттеров и сеттеров придумали синтаксис без вызова Object:
const animal = { get name() { return this._name }, set name(value) { this._name = value }}console.log(animal.name)// undefinedanimal.name = 'Кот'console.log(animal.name)// Кот
const animal = {
get name() {
return this._name
},
set name(value) {
this._name = value
}
}
console.log(animal.name)
// undefined
animal.name = 'Кот'
console.log(animal.name)
// Кот
Сеттеры могут понадобиться, например, для модификации значения при записи свойств. В примере ниже мы модифицируем дату и записываем в нужном формате.
const updatedAt = { get date() { return this._date }, set date(value) { this._date = new Intl.DateTimeFormat('en-US').format(value) }}
const updatedAt = {
get date() {
return this._date
},
set date(value) {
this._date = new Intl.DateTimeFormat('en-US').format(value)
}
}
Запишем дату и время в поле date:
updatedAt.date = new Date(2030, 11, 12)console.log(updatedAt.date)// 12/12/2030
updatedAt.date = new Date(2030, 11, 12)
console.log(updatedAt.date)
// 12/12/2030
И получим дату в нужном формате: 12.
Свойства с методами доступа дают нам все возможности обработки данных с помощью функций и простоту, характерную для работы с обычными свойствами.
Общие свойства
СкопированоОбщие свойства можно указывать в обоих типах дескрипторов.
enumerable
СкопированоСвойство определяет, является ли создаваемое свойство объекта видимым при перечислениях.
Создадим два свойства у объекта laptop — одно будет перечисляемым, а другое — нет:
const laptop = {}Object.defineProperty(laptop, 'processor', // Сделаем processor перечисляемым, как обычно { enumerable: true, value: 'Intel Core' })Object.defineProperty(laptop, 'touchID', // Сделаем `touchID` НЕперечисляемым { enumerable: false, value: true })console.log(laptop.touchID)// trueconsole.log(('touchID' in laptop))// trueconsole.log(laptop.hasOwnProperty('touchID'))// truefor (let key in laptop) { console.log(key, laptop[key])}// 'processor': 'Intel Core'
const laptop = {}
Object.defineProperty(laptop, 'processor',
// Сделаем processor перечисляемым, как обычно
{ enumerable: true, value: 'Intel Core' }
)
Object.defineProperty(laptop, 'touchID',
// Сделаем `touchID` НЕперечисляемым
{ enumerable: false, value: true }
)
console.log(laptop.touchID)
// true
console.log(('touchID' in laptop))
// true
console.log(laptop.hasOwnProperty('touchID'))
// true
for (let key in laptop) {
console.log(key, laptop[key])
}
// 'processor': 'Intel Core'
Заметьте, что laptop существует и имеет значение, но не отображается в цикле for (при этом, оно существует, если воспользоваться оператором in). «Перечислимое» означает: "будет учтено, если пройти перебором по свойствам объекта"».
configurable
СкопированоСвойство configurable определяет, доступно ли создаваемое свойство объекта для переконфигурирования.
Изменим значение configurable:
const laptop = {}Object.defineProperty(laptop, 'processor', { value: 'Intel Core', writable: true, // Запрещаем переконфигурирование! configurable: false, enumerable: true})console.log(laptop.processor)// Intel Corelaptop.processor = 'M1'console.log(laptop.processor)// 'M1'Object.defineProperty(laptop, 'processor', { value: 'M1 TOP', writable: true, configurable: true, enumerable: true})// TypeError: Cannot redefine property: processor
const laptop = {}
Object.defineProperty(laptop, 'processor', {
value: 'Intel Core',
writable: true,
// Запрещаем переконфигурирование!
configurable: false,
enumerable: true
})
console.log(laptop.processor)
// Intel Core
laptop.processor = 'M1'
console.log(laptop.processor)
// 'M1'
Object.defineProperty(laptop, 'processor', {
value: 'M1 TOP',
writable: true,
configurable: true,
enumerable: true
})
// TypeError: Cannot redefine property: processor
Попытка переписать дескриптор свойства processor приводит к ошибке Type, даже если вы находитесь не в строгом режиме.
Если для свойства уже задано configurable, то writable может быть изменено с true на false без ошибки, но не обратно в true если оно уже false.
А ещё configurable препятствует возможности использовать оператор delete для удаления существующего свойства. Ошибки не случится, но и свойство не удалится:
delete laptop.processorconsole.log(laptop)// { processor: 'M1' }
delete laptop.processor
console.log(laptop)
// { processor: 'M1' }
Периодически разработчику нужно защищать объекты от вмешательства извне. По ошибке легко изменить свойство объекта. Для защиты объектов от подобных изменений и управления их иммутабельностью предлагается использовать дескрипторы, такие как writable и configurable, сеттеры, а также методы Object, Object, и Object для ограничения доступа к объекту целиком.
На практике
Скопированосоветует
Скопировано🛠 В жизни проще использовать именно Object и Object, потому что чаще всего нужно ограничить доступ ко всему объекту целиком.
Сначала скажу, что есть метод Object, чтобы проще объяснить принцип работы Object.
Object запрещает добавление новых свойств объекта, но, в то же время, оставляет существующие свойства нетронутыми.
const laptop = { displaySize: 15}Object.preventExtensions(laptop)laptop.storage = 256console.log(laptop.storage)// undefined
const laptop = {
displaySize: 15
}
Object.preventExtensions(laptop)
laptop.storage = 256
console.log(laptop.storage)
// undefined
В нестрогом режиме создание storage завершится неудачей без ошибок. В строгом режиме это приведёт к ошибке Type.
Метод Object запечатывает переданный ему объект, одновременно запрещая добавление новых свойств и конфигурирование существующих свойств. Значения свойств при этом изменять можно. Другими словами, Object является эквивалентом применения Object к объекту и configurable к его свойствам.
Object, в свою очередь, замораживает объект, одновременно запрещая добавление новых свойств и изменение существующих свойств. Это соответствует применению Object к объекту и writable к его свойствам.
Обратим внимание, что метод поверхностный. У замороженного объекта остаётся возможность изменять вложенные объекты. На MDN есть пример глубокой заморозки — метод deep. Он позволяет сделать полностью иммутабельный (неизменяемый) объект. При этом невозможно сделать иммутабельными Date, Map или Set.
Этот подход даёт наивысший уровень иммутабельности, который вы можете получить для самого объекта.
Методы Object, Object и Object возвращают ссылку на тот же объект, что был им передан:
const foo = {}const bar = Object.freeze(foo)foo === bar// true
const foo = {}
const bar = Object.freeze(foo)
foo === bar
// true
const frozen = Object.freeze({ foo: 'bar' })
const frozen = Object.freeze({ foo: 'bar' })
🛠 Для объявления нескольких свойств воспользуйтесь статическим методом Object:
const laptop = {}Object.defineProperties(laptop, { os: { value: 'MacOS', enumerable: true }, age: { value: 10, enumerable: false }})const result = Object.keys(laptop)console.log(result)// ['os']
const laptop = {}
Object.defineProperties(laptop, {
os: {
value: 'MacOS',
enumerable: true
},
age: {
value: 10,
enumerable: false
}
})
const result = Object.keys(laptop)
console.log(result)
// ['os']
🛠 Получение значений дескрипторов для конкретного свойства объекта:
const source = { name: 'Doka', sections: ['HTML', 'CSS', 'JS', 'Tools', 'Recipes'], themes: ['light']}const nameDescriptors = Object.getOwnPropertyDescriptor(source, 'name')console.log(nameDescriptors)//{// 'value':'Doka',// 'writable':true,// 'enumerable':true,// 'configurable':true//}
const source = {
name: 'Doka',
sections: ['HTML', 'CSS', 'JS', 'Tools', 'Recipes'],
themes: ['light']
}
const nameDescriptors = Object.getOwnPropertyDescriptor(source, 'name')
console.log(nameDescriptors)
//{
// 'value':'Doka',
// 'writable':true,
// 'enumerable':true,
// 'configurable':true
//}
🛠 Получение значений дескрипторов для всех свойств объекта:
const allPropertyDescriptors = Object.getOwnPropertyDescriptors(source)console.log(allPropertyDescriptors)
const allPropertyDescriptors = Object.getOwnPropertyDescriptors(source)
console.log(allPropertyDescriptors)
Получим следующий ответ:
{ name: { value: 'Doka', writable: true, enumerable: true, configurable: true, }, sections: { value: ['HTML', 'CSS', 'JS', 'Tools', 'Recipes'], writable: true, enumerable: true, configurable: true, }, themes: { value: ['light'], writable: true, enumerable: true, configurable: true, },}
{
name: {
value: 'Doka',
writable: true,
enumerable: true,
configurable: true,
},
sections: {
value: ['HTML', 'CSS', 'JS', 'Tools', 'Recipes'],
writable: true,
enumerable: true,
configurable: true,
},
themes: {
value: ['light'],
writable: true,
enumerable: true,
configurable: true,
},
}
Если передан пустой объект без свойств, то получим пустой объект:
const user = {}const userDescriptors = Object.getOwnPropertyDescriptors(user)console.log(userDescriptors)// {}
const user = {}
const userDescriptors = Object.getOwnPropertyDescriptors(user)
console.log(userDescriptors)
// {}
🛠 Object определяет, был ли объект заморожен. Возвращает true, если добавление, удаление или изменение свойств запрещено и для текущих свойств установлено configurable, writable.