Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Property descriptor (дескриптор властивості)». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Property descriptor це внутрішній об'єкт, який описує поведінку властивості об'єкта: чи можна її змінювати, перелічувати, видаляти і чи має вона гетер або сетер.** Це метадані про властивість, а не лише її значення. Властивості бувають двох видів: data property з полями `value` і `writable`, та accessor property з функціями `get` і `set`. Обидва види додатково мають `enumerable` і `configurable`. Читають дескриптор через `Object.getOwnPropertyDescriptor(obj, key)`, задають через `Object.defineProperty`. ```javascript Object.defineProperty(user, 'role', { value: 'admin', writable: false }); ``` **Ключове:** у звичайно створеної властивості всі прапорці дорівнюють `true`, а у створеної через `Object.defineProperty` пропущені прапорці дорівнюють `false`.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**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 }); ``` 2. **Приховані властивості, які не потрапляють у перебір і в `JSON.stringify`:** ```javascript Object.defineProperty(obj, '_id', { value: 42, enumerable: false }); ``` 3. **Реалізація реактивності:** Vue 2, наприклад, використовував `get` і `set` у дескрипторах, щоб відстежувати зміни даних. 4. **Імітація приватних полів** (до появи синтаксису `#`): ```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` її покаже.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.