Object Property Descriptors
debt(d8/e3/b3/t7)
Closest to 'silent in production until users hit it' (d9), pulled to d8: detection_hints.automated is 'no' and there is no linter rule for descriptor flag defaults. A property accidentally created read-only or hidden fails silently (writable:false fails silently in sloppy mode, non-enumerable fields silently dropped from JSON.stringify), surfacing only when an update silently fails or serialization misbehaves.
Closest to 'simple parameterised fix' (e3): quick_fix says to set writable, enumerable, configurable explicitly in the defineProperty descriptor object. This is a localized correction to the descriptor literal, slightly more than a one-line swap since multiple flags and call sites may need auditing.
Closest to 'localised tax' (b3): applies_to web/node/library but in practice descriptors are used in specific metaprogramming/reflection spots rather than across every module. The component using defineProperty pays the cognitive cost; the rest of the codebase is largely unaffected unless configurable:false locks something needed elsewhere.
Closest to 'serious trap' (t7): misconception states developers assume defineProperty defaults match normal assignment, but it defaults writable/enumerable/configurable all to false — the opposite of how plain property assignment behaves. This contradicts the intuition built from normal object property creation, making the obvious approach wrong.
Also Known As
TL;DR
Explanation
Getters and setters defined in object literal syntax produce accessor descriptors that are enumerable and configurable, whereas accessors defined in class bodies sit on the prototype and are non-enumerable (but still configurable).
Common Misconception
Why It Matters
Common Mistakes
- Forgetting that Object.defineProperty defaults all flags to false, creating an unexpectedly locked property.
- Using spread or Object.assign to clone and losing getters, setters, and non-enumerable flags.
- Assuming writable: false throws on reassignment in sloppy mode when it fails silently.
- Setting configurable: false too early, then being unable to delete or redefine the property.
- Expecting non-enumerable properties to be hidden from Object.getOwnPropertyNames or direct access.
Avoid When
- Simple objects where normal assignment with default writable/enumerable/configurable is sufficient.
- When the property model is well-served by class fields or plain object literals.
- Hot paths where defineProperty per property adds avoidable overhead over direct assignment.
When To Use
- Defining non-enumerable internal fields you want hidden from serialization and iteration.
- Creating computed accessor properties with get/set on existing objects.
- Cloning objects while preserving getters, setters, and exact descriptor flags.
Code Examples
config.timeout = 10000; // Silently ignored in sloppy mode; throws TypeError in strict mode/modules
console.log(config.timeout); // 5000
const config = {};
// Be explicit about every flag you care about:
Object.defineProperty(config, 'timeout', {
value: 5000,
writable: true,
enumerable: true,
configurable: true,
});
config.timeout = 10000;
console.log(config.timeout); // 10000
console.log(JSON.stringify(config)); // {"timeout":10000}
// Clone faithfully, preserving getters and flags:
const src = { get id() { return 42; } };
const copy = Object.create(
Object.getPrototypeOf(src),
Object.getOwnPropertyDescriptors(src)
);
console.log(Object.getOwnPropertyDescriptor(copy, 'id').get); // function