Property Descriptors, Getters/Setters & defineProperty
Property sirf key-value nahi hai — har property ke peeche ek secret contract hai, getter/setter spy lagate hain.
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 101When 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)
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 negativeIf 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.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'
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: falseprevents 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
Want to track your progress?
Log in to save your place and pick up where you left off.
Progress track karna chahte ho?
Login karo apni progress save karne ke liye aur jahan chhoda tha wahan se shuru karo.
Login