Property Descriptors in JavaScript

Introduction

Every object property has hidden attributes: Is it writable? Enumerable in loops? Configurable for deletion? Object.defineProperty and Object.getOwnPropertyDescriptor expose these controls—used by frameworks and for defining getters without accidental overwrites.

Prerequisites

Default Descriptor

Data properties created in literals are usually writable, enumerable, and configurable:

javascript
// Normal assignment
const item = { price: 10 };
item.price = 12;
console.log(item.price);

Read Descriptor

javascript
// Inspect attributes
const user = { name: "Ada" };
const desc = Object.getOwnPropertyDescriptor(user, "name");
 
console.log(desc);

Typical data descriptor fields: value, writable, enumerable, configurable.

defineProperty — Control Behavior

javascript
// Non-writable property
const config = {};
Object.defineProperty(config, "apiUrl", {
  value: "https://api.example.com",
  writable: false,
  enumerable: true,
  configurable: false,
});
 
config.apiUrl = "https://other";
console.log(config.apiUrl);

Silent failure in sloppy mode; TypeError in strict mode when assigning non-writable fields.

Accessor Descriptor (Getter / Setter)

javascript
// Computed property with getter
const account = {
  _balance: 0,
};
 
Object.defineProperty(account, "balance", {
  get() {
    return this._balance;
  },
  set(value) {
    if (value < 0) throw new Error("negative balance");
    this._balance = value;
  },
  enumerable: true,
  configurable: true,
});
 
account.balance = 100;
console.log(account.balance);

Class getter/setter syntax is sugar over this model.

enumerable: false Hides from for...in

javascript
const obj = { visible: 1 };
Object.defineProperty(obj, "hidden", {
  value: 2,
  enumerable: false,
});
 
for (const key in obj) {
  console.log(key);
}
console.log(Object.keys(obj));

Object.assign and spread copy enumerable own properties only.

Object.defineProperties

javascript
// Batch define
const meta = {};
Object.defineProperties(meta, {
  version: { value: "1.0.0", writable: false },
  build: { value: 42, writable: false },
});
console.log(meta.version, meta.build);

Mini Example: Read-Only ID Field

javascript
function createRecord(id, title) {
  const record = { title };
  Object.defineProperty(record, "id", {
    value: id,
    writable: false,
    enumerable: true,
    configurable: false,
  });
  return record;
}
 
const row = createRecord(101, "Widget");
console.log(row.id, row.title);

FAQ

Descriptors vs private # fields?

Private fields are language-level privacy; descriptors are per-property flags on ordinary objects.

Can I change a non-configurable property?

Not delete or reconfigure; may still change value if writable: true.

Why learn this as a beginner?

Helps debug library behavior and read docs for Object.freeze and similar APIs.

What comes next?

Object extensibility.