Suggest an editImprove this articleRefine the answer for “Property descriptor”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**A property descriptor is an internal object that describes how a property behaves: whether it can be changed, enumerated or deleted, and whether it has a getter or a setter.** It is metadata about the property, not just its value. Properties come in two kinds: a data property with `value` and `writable`, and an accessor property with `get` and `set` functions. Both kinds also carry `enumerable` and `configurable`. You read a descriptor with `Object.getOwnPropertyDescriptor(obj, key)` and define one with `Object.defineProperty`. ```javascript Object.defineProperty(user, 'role', { value: 'admin', writable: false }); ``` **Key point:** a normally created property has every flag set to `true`, while a property created through `Object.defineProperty` gets `false` for every flag you omit.Shown above the full answer for quick recall.Answer (EN)Image**A property descriptor is an internal object that describes how an object's property behaves: whether it can be changed, enumerated or deleted, and whether it has a getter or a setter.** Put simply, it is metadata about the property: not only its value, but also the flags that define how you are allowed to work with it. ## Theory ### TL;DR - A descriptor describes not the value, but the **rules for working** with a property. - Properties come in two types: a **data property** (`value`, `writable`) and an **accessor property** (`get`, `set`). - Flags common to both: `enumerable` (is it visible when iterating) and `configurable` (can it be deleted or reconfigured). - Reading: `Object.getOwnPropertyDescriptor` and `Object.getOwnPropertyDescriptors`. - Writing: `Object.defineProperty` and `Object.defineProperties`. - An ordinary property has every flag `true`, while a property created through `defineProperty` gets `false` for every flag you omit. ### Quick example ```javascript const user = { name: 'Tim' }; const descriptor = Object.getOwnPropertyDescriptor(user, 'name'); console.log(descriptor); // { // value: 'Tim', // writable: true, // enumerable: true, // configurable: true // } ``` All of these flags default to `true` when the property is created the ordinary way. ### Kinds of properties and the shape of a descriptor Properties come in two types: 1. A **data property** stores a concrete value in the `value` field. 2. An **accessor property** defines `get` and `set` functions. For a data property: ```javascript { value: any, // the value writable: boolean, // can value be changed enumerable: boolean, // is it visible when iterating (for...in, Object.keys) configurable: boolean // can it be deleted or its descriptor changed } ``` For an accessor property: ```javascript { get: function | undefined, // getter set: function | undefined, // setter enumerable: boolean, configurable: boolean } ``` A single descriptor cannot carry `value` or `writable` together with `get` or `set`: it is either data or an accessor. ### Defining a property by hand ```javascript const user = {}; Object.defineProperty(user, 'role', { value: 'admin', writable: false, // the value cannot be changed enumerable: false, // does not show up when iterating configurable: false // cannot be deleted or reconfigured }); console.log(user.role); // 'admin' user.role = 'user'; console.log(user.role); // 'admin', the value did not change ``` Let us check enumerability and configurability: ```javascript const user = {}; Object.defineProperty(user, 'name', { value: 'Tim', enumerable: false, configurable: false }); console.log(Object.keys(user)); // [], not enumerated delete user.name; // will not be deleted console.log(user.name); // 'Tim' ``` In sloppy mode such attempts are silently ignored, while under `'use strict'` they throw a `TypeError`. Defining many properties at once: ```javascript const user = {}; Object.defineProperties(user, { name: { value: 'Alex', writable: true, enumerable: true }, age: { value: 25, writable: false } }); ``` Getting every descriptor of an object in one go: ```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 } // } ``` ### A getter and a setter in a descriptor ```javascript const person = {}; let _age = 25; Object.defineProperty(person, 'age', { get() { return _age; }, set(value) { if (value < 0) throw new Error('Age cannot be negative'); _age = value; }, enumerable: true, configurable: true }); console.log(person.age); // 25 person.age = 30; // calls the setter console.log(person.age); // 30 ``` From the outside `person.age` looks like an ordinary property, but every read and write goes through your functions, and that is exactly where validation belongs. ### Real world uses 1. **Creating read-only properties:** ```javascript Object.defineProperty(config, 'API_KEY', { value: '12345', writable: false }); ``` 2. **Hidden properties that stay out of iteration and of `JSON.stringify`:** ```javascript Object.defineProperty(obj, '_id', { value: 42, enumerable: false }); ``` 3. **Implementing reactivity:** Vue 2, for example, used `get` and `set` inside descriptors to track data changes. 4. **Emulating private fields** (before the `#` syntax existed): ```javascript Object.defineProperty(obj, 'secret', { value: 'hidden', enumerable: false }); ``` A summary of the fields: | Descriptor field | What it is for | | --- | --- | | `value` | the value itself | | `writable` | whether `value` can be changed | | `enumerable` | whether it is visible when iterating | | `configurable` | whether it can be deleted or redefined | | `get` | the getter function | | `set` | the setter function | ### Common mistakes - **Expecting omitted flags to be `true`.** In `Object.defineProperty` everything you do not specify is `false`, so the property unexpectedly becomes non writable and non enumerable. - **Confusing `writable: false` with deep immutability.** Only the reference itself is locked: the fields of the object it points to can still be changed. - **Combining `value` with `get` or `set` in one descriptor.** That is an immediate `TypeError`, a property is either data or an accessor. - **Not noticing the silent failure.** Without `'use strict'`, writing to a `writable: false` property does nothing and throws nothing, which looks like a mysterious bug. - **Setting `configurable: false` too early.** There is no way back: such a property can no longer be reconfigured or deleted. - **Assuming `Object.assign` or spread carry descriptors over.** They copy only the values of own enumerable properties, and getters are executed in the process. To preserve descriptors use `Object.defineProperties(target, Object.getOwnPropertyDescriptors(source))`. - **Forgetting that `enumerable: false` hides a property from `Object.keys`, `for...in` and `JSON.stringify`,** but does not make it private: `Object.getOwnPropertyNames` still reveals it.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.