Skip to main content

Property descriptor (дескриптор властивості)

Property descriptor (дескриптор властивості) це внутрішній об'єкт, який описує поведінку властивості об'єкта: чи можна її змінювати, перелічувати, видаляти і чи має вона гетер або сетер. Простіше кажучи, це метадані про властивість: не лише її значення, а й прапорці, що визначають, як із нею можна працювати.

Теорія

TL;DR

  • Дескриптор описує не значення, а правила роботи з властивістю.
  • Властивості бувають двох типів: data property (value, writable) і accessor property (get, set).
  • Спільні прапорці обох типів: enumerable (чи видно при переборі) і configurable (чи можна видалити або переналаштувати).
  • Читання: Object.getOwnPropertyDescriptor і Object.getOwnPropertyDescriptors.
  • Запис: Object.defineProperty і Object.defineProperties.
  • У звичайної властивості всі прапорці true, у створеної через defineProperty пропущені прапорці стають false.

Швидкий приклад

javascript
const user = { name: 'Tim' }; const descriptor = Object.getOwnPropertyDescriptor(user, 'name'); console.log(descriptor); // { // value: 'Tim', // writable: true, // enumerable: true, // configurable: true // }

Усі ці прапорці за замовчуванням дорівнюють true, якщо властивість створено звичайним способом.

Види властивостей і структура дескриптора

Властивості бувають двох типів:

  1. Data property (властивість-дані) зберігає конкретне значення у полі value.
  2. Accessor property (властивість-аксесор) визначає функції get і set.

Для властивості-даних:

javascript
{ value: any, // значення writable: boolean, // чи можна змінювати value enumerable: boolean, // чи видно при переборі (for...in, Object.keys) configurable: boolean // чи можна видалити або змінити дескриптор }

Для властивості-аксесора:

javascript
{ get: function | undefined, // гетер set: function | undefined, // сетер enumerable: boolean, configurable: boolean }

Один дескриптор не може одночасно мати value або writable і get або set: це або дані, або аксесор.

Як задати властивість вручну

javascript
const user = {}; Object.defineProperty(user, 'role', { value: 'admin', writable: false, // не можна змінювати значення enumerable: false, // не з'являється при переборі configurable: false // не можна видалити або переналаштувати }); console.log(user.role); // 'admin' user.role = 'user'; console.log(user.role); // 'admin', значення не змінилося

Перевіримо перелічуваність і конфігурованість:

javascript
const user = {}; Object.defineProperty(user, 'name', { value: 'Tim', enumerable: false, configurable: false }); console.log(Object.keys(user)); // [], не перелічується delete user.name; // не видалиться console.log(user.name); // 'Tim'

У нестрогому режимі такі спроби мовчки ігноруються, у 'use strict' вони кидають TypeError.

Масове визначення властивостей:

javascript
const user = {}; Object.defineProperties(user, { name: { value: 'Alex', writable: true, enumerable: true }, age: { value: 25, writable: false } });

Отримати всі дескриптори об'єкта одразу:

javascript
const obj = { a: 1, b: 2 }; console.log(Object.getOwnPropertyDescriptors(obj)); // { // a: { value: 1, writable: true, enumerable: true, configurable: true }, // b: { value: 2, writable: true, enumerable: true, configurable: true } // }

Гетер і сетер у дескрипторі

javascript
const person = {}; let _age = 25; Object.defineProperty(person, 'age', { get() { return _age; }, set(value) { if (value < 0) throw new Error('Вік не може бути від'ємним'); _age = value; }, enumerable: true, configurable: true }); console.log(person.age); // 25 person.age = 30; // викликає setter console.log(person.age); // 30

Ззовні person.age виглядає як звичайна властивість, але кожне читання і запис проходять через ваші функції, і саме тут зручно робити валідацію.

Застосування в реальних задачах

  1. Створення властивостей «тільки для читання»:
javascript
Object.defineProperty(config, 'API_KEY', { value: '12345', writable: false });
  1. Приховані властивості, які не потрапляють у перебір і в JSON.stringify:
javascript
Object.defineProperty(obj, '_id', { value: 42, enumerable: false });
  1. Реалізація реактивності: Vue 2, наприклад, використовував get і set у дескрипторах, щоб відстежувати зміни даних.

  2. Імітація приватних полів (до появи синтаксису #):

javascript
Object.defineProperty(obj, 'secret', { value: 'hidden', enumerable: false });

Підсумкова таблиця полів:

Поле дескриптораДля чого потрібне
valueсаме значення
writableчи можна змінити value
enumerableчи видно при переборі
configurableчи можна видалити або перевизначити
getфункція-гетер
setфункція-сетер

Типові помилки

  • Очікувати, що пропущені прапорці будуть true. У Object.defineProperty усе, чого ви не вказали, дорівнює false, тож властивість несподівано стає нередагованою і неперелічуваною.
  • Плутати writable: false із глибокою незмінністю. Замкнути можна лише саме посилання: поля об'єкта, на який воно вказує, і далі змінюються.
  • Комбінувати value з get або set в одному дескрипторі. Це одразу TypeError, властивість може бути або даними, або аксесором.
  • Не помічати тихого провалу. Без 'use strict' запис у writable: false властивість нічого не робить і не кидає помилки, що виглядає як загадковий баг.
  • Ставити configurable: false завчасно. Назад дороги немає: переналаштувати або видалити таку властивість уже не вийде.
  • Розраховувати, що Object.assign або spread перенесуть дескриптори. Вони копіюють лише значення власних перелічуваних властивостей, гетери при цьому виконуються. Щоб зберегти дескриптори, використовуйте Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)).
  • Забувати, що enumerable: false ховає властивість від Object.keys, for...in і JSON.stringify, але не робить її приватною: Object.getOwnPropertyNames її покаже.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.