Skip to content
Foxipa

Suggest an edit

Tell us what should be corrected, clarified, or improved. Submission will be connected to Foxipa’s contribution service later.

No information is sent anywhere yet. Your suggestion is only prepared in your browser.

Learning

JavaScript Property Descriptors, Accessors, and Object Integrity

Understand data and accessor property descriptors, getters and setters, precise property definition, and object integrity controls.

Levels: Intermediate ~30 min read

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.

Interactive Runnable example
Edit

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.

javascript descriptor-kinds.js
1
2
3
4
5
6
7
const data = { value: 10 };
Object.defineProperty(data, "count", { value: 10, writable: true });

const accessors = {
  get count() { return this._count ?? 0; },
  set count(value) { this._count = value; }
};
Interactive Runnable example
Edit

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.

Interactive Runnable example
Edit

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.

Interactive Runnable example
Edit

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.

Interactive Runnable example
Edit

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.

Interactive Runnable example
Edit

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]].

Interactive Runnable example
Edit

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.

Interactive Runnable example
Edit

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.

Interactive Runnable example
Edit

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.

Interactive Runnable example
Edit

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().

Interactive Runnable example
Edit

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.

Interactive Runnable example
Edit

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.”

Interactive Runnable example
Edit

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.

Interactive Runnable example
Edit

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.

Interactive Runnable example
Edit

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. urlECMA-262 — current language specificationhttps://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.

Was this page helpful?

Mark this learning resource complete to track your learning progress.