Chapter 7.2☕ 19 min

Property Descriptors, Getters/Setters & defineProperty

Property sirf key-value nahi hai — har property ke peeche ek secret contract hai, getter/setter spy lagate hain.

01Property Descriptors — The Secret Contract

Every property has a descriptor object with attributes: value, writable, enumerable, and configurable.

Data Descriptor: Has value and writable.

Accessor Descriptor: Has get and set functions.

Both share: enumerable and configurable.

Object.getOwnPropertyDescriptor(obj, 'key') reveals the descriptor. Object.defineProperty(obj, 'key', descriptor) defines or modifies a property.

const user = { name: 'Sai' };

// Get the secret contract
const desc = Object.getOwnPropertyDescriptor(user, 'name');
console.log(desc);
// { value: 'Sai', writable: true, enumerable: true, configurable: true }

// Define a property with a custom contract
Object.defineProperty(user, 'id', {
  value: 101,
  writable: false,     // Cannot change id
  enumerable: true,    // Shows up in for...in
  configurable: false  // Cannot delete or re-configure
});

console.log(user.id); // 101
user.id = 999;        // Silently fails (throws in strict mode!)
console.log(user.id); // Still 101
02defineProperty Defaults Trap

When you create a property via assignment (obj.x = 1), defaults are: writable: true, enumerable: true, configurable: true.

When you create a property via defineProperty, defaults are: writable: false, enumerable: false, configurable: false.

This is a massive gotcha! Properties created via defineProperty are locked down by default.

enumerable: false means the property won't show in for...in, Object.keys(), or JSON.stringify().

const obj1 = {};
obj1.a = 1; 
// Descriptor: { value: 1, writable: true, enumerable: true, configurable: true }

const obj2 = {};
Object.defineProperty(obj2, 'a', { value: 1 });
// Descriptor: { value: 1, writable: false, enumerable: false, configurable: false }

// The trap: obj2.a is invisible and immutable!
console.log(Object.keys(obj1)); // ['a']
console.log(Object.keys(obj2)); // [] — empty! Not enumerable!

obj1.a = 2; // Works
obj2.a = 2; // Fails silently (strict mode throws TypeError)
V8 Internals: V8 stores property descriptors in the object's Hidden Class (Map). A non-writable property has a different field type than a writable one. Non-enumerable properties are stored in the same structure but are skipped during iteration via a side-table that tracks enumeration visibility.
03Getters & Setters — Property Spies

Getters (get) and Setters (set) are Accessor Descriptors.

They look like properties but execute functions when read or written.

A property CANNOT be both a Data Descriptor (has value) and an Accessor Descriptor (has get/set).

Getters are useful for computed properties. Setters are useful for validation.

Define them in object literals or via defineProperty.

const user = {
  firstName: 'Sai',
  lastName: 'Kumar',
  
  // Getter — computed on access
  get fullName() {
    return this.firstName + ' ' + this.lastName;
  },
  
  // Setter — validates on write
  set age(val) {
    if (val < 0) throw new Error('Age cannot be negative');
    this._age = val;
  },
  get age() {
    return this._age || 0;
  }
};

console.log(user.fullName); // 'Sai Kumar' (executes getter)
user.age = 25;              // Executes setter
console.log(user.age);      // 25
// user.age = -5;           // Error: Age cannot be negative
04configurable: false — The Lock

If configurable: false, you CANNOT delete the property.

You CANNOT change the descriptor (except writable: true → false).

You CANNOT change a data property to an accessor property or vice versa.

Once configurable: false, it's almost permanent.

Object.defineProperty on a non-configurable property throws TypeError.

const obj = {};
Object.defineProperty(obj, 'lock', {
  value: 42,
  writable: true,
  configurable: false
});

// Can change value (writable is true)
obj.lock = 100; 

// Cannot delete
delete obj.lock; // false (throws in strict mode)

// Cannot reconfigure
// Object.defineProperty(obj, 'lock', { enumerable: true }); // TypeError!

// CAN change writable from true to false (special exception)
Object.defineProperty(obj, 'lock', { writable: false });
obj.lock = 999; // Fails! Now it's fully locked.
05Immutability: preventExtensions, Seal, Freeze

Object.preventExtensions(obj): Cannot ADD new properties. Can modify/delete existing.

Object.seal(obj): preventExtensions + all properties configurable: false. Cannot add or delete. Can modify values.

Object.freeze(obj): seal + all properties writable: false. Cannot add, delete, or modify. Fully immutable.

All three affect only the TOP level. Nested objects are still mutable (shallow freeze).

Check status: Object.isExtensible(), Object.isSealed(), Object.isFrozen().

// preventExtensions — can't add, can modify/delete
const ext = { a: 1 };
Object.preventExtensions(ext);
ext.a = 2;       // OK
delete ext.a;    // OK
// ext.b = 3;    // Fails

// seal — can't add/delete, can modify
const sealed = Object.seal({ x: 1 });
sealed.x = 99;   // OK
// delete sealed.x; // Fails
// sealed.y = 2;    // Fails

// freeze — fully locked
const frozen = Object.freeze({ pi: 3.14 });
// frozen.pi = 3; // Fails
// delete frozen.pi; // Fails

// THE SHALLOW FREEZE TRAP
const user = Object.freeze({
  name: 'Sai',
  address: { city: 'Hyd' }
});
user.name = 'Ali';           // Fails
user.address.city = 'Mumbai'; // WORKS! Nested object is not frozen!
console.log(user.address.city); // 'Mumbai'
🚀 V8 Optimization: Object.freeze() helps V8 optimize aggressively. TurboFan knows the object's shape and values will never change, allowing it to inline property values directly into machine code. Use it for constants and configuration objects.

Lo kar liya — Key Points:

  • ✅ Every property has a descriptor with writable, enumerable, and configurable attributes
  • ✅ defineProperty defaults are all FALSE (unlike normal assignment which defaults to true) — a common trap
  • ✅ Getters and Setters are Accessor Descriptors that execute code on property read/write
  • configurable: false prevents deletion and re-configuration; it's nearly permanent
  • ✅ Object.freeze makes an object fully immutable at the top level, but it is a shallow freeze
  • ✅ Use freeze for constants to help V8 optimize; use seal for fixed-shape objects
Course Search
Search across all chapters & stages
📖

Search the course

Type any topic — branching, stash, rebase, hooks — and jump straight to that chapter.

merge branchesgit stashundo commitrebase