JavaScript Property Descriptors, Accessors, and Object Integrity
Understand data and accessor property descriptors, getters and setters, precise property definition, and object integrity controls.
Intermediate
JavaScript properties carry more information than just a key and a value. A property can be a data property with a stored value, or an accessor property whose getter and setter functions run when the property is read or written. Descriptors expose these attributes so you can inspect and deliberately define object behavior.
A property descriptor is the property’s definition
For an ordinary object, a property descriptor records attributes such as value, writable, enumerable, and configurable for a data property. Accessor properties instead use get, set, enumerable, and configurable. Object.getOwnPropertyDescriptor() lets you inspect an own property without confusing its descriptor with the value produced by reading the property.
Output
Ready to run.
Quick check
Which operation returns the descriptor for an own property?
const result = ;
Data properties and accessor properties are different kinds
A data property stores a value. An accessor property stores a getter and/or setter function instead. A property descriptor must describe one kind or the other; combining data fields such as value with accessor fields such as get is not a valid descriptor.
|
|
Output
Ready to run.
Quick check
Which pair identifies an accessor descriptor?
const result = ;
Object.defineProperty gives exact control
Object.defineProperty() creates or changes one own property using a descriptor. This is more precise than ordinary assignment because you can choose the property attributes explicitly. Be careful with omitted attributes: for a newly created property, descriptor fields you omit receive their specified default values rather than the defaults you may expect from an object literal.
Output
Ready to run.
Quick check
For a newly created property defined with only { value: 42 }, what is enumerable?
const result = ;
Writable controls changing a data value
The writable attribute controls whether the stored value of a data property can be changed through ordinary property-setting operations. It does not mean that the property disappears from the object, and it does not apply to accessor properties. A non-writable property can still be readable.
Output
Ready to run.
Quick check
What does writable: false prevent for a data property?
const result = ;
Enumerable controls ordinary property enumeration
Enumerable is about whether a property participates in enumeration operations that consider enumerable own properties, such as Object.keys(). A non-enumerable property still exists and can still be read directly. Do not use enumerability as a synonym for visibility or privacy.
Output
Ready to run.
Quick check
Which statement is true for a non-enumerable own property?
const result = ;
Configurable controls structural changes
Configurable determines whether a property descriptor can generally be changed or the property can be deleted. A property that is non-configurable has important restrictions on later redefinition. This is a stronger constraint than writable: false, because configurability governs the property definition itself.
Output
Ready to run.
Quick check
What does configurable primarily control?
const result = ;
Getters run when the property is read
A getter is a function used by an accessor property to produce a value when the property is read. The syntax object.value looks the same to the caller, but the operation executes the getter rather than retrieving a stored [[Value]].
Output
Ready to run.
Quick check
When does a getter normally run?
const result = ;
Setters run when the property is assigned
A setter receives the value supplied by an assignment to its accessor property. A setter can validate, normalize, store elsewhere, or trigger other behavior. If an accessor has no setter, an attempted write cannot update that accessor through the setter path.
Output
Ready to run.
Quick check
What receives the right-hand side of meter.value = 8?
const result = ;
Getters and setters can use this
Accessor functions are ordinary function objects. When a getter or setter is invoked through a property access, this is associated with the receiver of that property operation. This makes accessors useful for behavior that is shared through a prototype while operating on individual instances.
Output
Ready to run.
Quick check
If a getter is found on a prototype and read as child.value, what does this usually allow the getter to use as this?
const result = ;
Descriptor inspection does not invoke the getter
Object.getOwnPropertyDescriptor() reports the descriptor itself. For an accessor property, its get and set fields contain the function objects (or undefined). Inspecting the descriptor is therefore different from reading object.property, which would execute the getter.
Output
Ready to run.
Quick check
Does Object.getOwnPropertyDescriptor(object, "value") call an accessor getter?
const result = ;
Object.defineProperties defines several properties
Object.defineProperties() applies multiple property descriptors in one operation. Each descriptor is still interpreted using the same data-versus-accessor and attribute rules as Object.defineProperty().
Output
Ready to run.
Quick check
Which API defines several own properties from a descriptor map?
const result = ;
Object integrity has levels
JavaScript provides progressively stronger restrictions on object mutation. Object.preventExtensions() stops new own properties from being added. Object.seal() also makes existing own properties non-configurable. Object.freeze() additionally makes existing data properties non-writable. These operations affect the object itself; they do not recursively freeze nested objects.
Output
Ready to run.
Quick check
Which operation makes existing own properties non-configurable and also prevents adding new ones?
const result = ;
Freeze is shallow
Object.freeze() applies integrity restrictions to the target object’s own properties. If one of those properties contains another object, that nested object remains independently mutable unless it is frozen too. This is why “frozen” does not mean “deeply immutable.”
Output
Ready to run.
Quick check
After Object.freeze(state), what happens to an independently mutable nested object stored in state.settings?
const result = ;
Strict mode makes failed writes visible
Integrity restrictions can make a write fail. In non-strict code, some failed assignments to restricted properties do not throw and instead have no effect. Strict mode turns many such failed writes into TypeError exceptions. This difference matters when teaching or debugging object integrity.
Output
Ready to run.
Quick check
In strict mode, what normally happens when code assigns a different value to a non-writable data property?
const result = ;
Integrity checks are observations, not recursive guarantees
Object.isExtensible(), Object.isSealed(), and Object.isFrozen() report the integrity state of the object you pass. They do not promise anything about nested objects. Use these checks when your code needs to verify a particular object’s state rather than assuming that a related object has the same integrity level.
Output
Ready to run.
Quick check
What does Object.isFrozen(outer) tell you?
const result = ;
Authoring rules
Use ordinary object syntax when ordinary defaults are sufficient. Use defineProperty or defineProperties when the descriptor attributes are part of the design. Remember the data/accessor split: value and writable belong to data descriptors, while get and set belong to accessor descriptors. Treat enumerable and configurable as behavior controls, not as privacy mechanisms. Prefer freeze, seal, or preventExtensions when an API explicitly needs an integrity boundary, and document whether nested objects remain mutable.
Practice
Build a small configuration object with one public enumerable field, one non-enumerable identifier, and one accessor that validates a value. Inspect all three descriptors. Then create a nested object, freeze the outer object, and demonstrate that the nested object is still mutable. Finally, change the example to freeze both levels and observe the difference.
Specification references
The current ECMA-262 specification defines property attributes such as [[Value]], [[Writable]], [[Get]], [[Set]], [[Enumerable]], and [[Configurable]], as well as the object internal methods that implement property operations. It also defines Object.defineProperty(), Object.getOwnPropertyDescriptor(), Object.preventExtensions(), Object.seal(), Object.freeze(), and their integrity checks. urlECMA-262 — current language specificationhttps://tc39.es/ecma262/
Next step
The next metaprogramming layer is Proxy and Reflect: intercepting object internal operations, understanding proxy traps and their invariants, and using Reflect to delegate those operations explicitly.
Thanks for your feedback.
Mark this learning resource complete to track your learning progress.