Docs
README
7.3 Property Descriptors
Table of Contents
- ā¢Introduction
- ā¢Property Descriptor Types
- ā¢Getting Property Descriptors
- ā¢Defining Properties
- ā¢Configurable Attribute
- ā¢Enumerable Attribute
- ā¢Writable Attribute
- ā¢Accessor Properties
- ā¢Object Freezing and Sealing
- ā¢Best Practices
Introduction
Every property in JavaScript has more than just a value. Properties have hidden attributes called descriptors that control their behavior.
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā Property Anatomy ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā ā
ā const obj = { name: "Alice" }; ā
ā ā
ā Property "name" has: ā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
ā ā value: "Alice" ā The actual value ā ā
ā ā writable: true ā Can be changed? ā ā
ā ā enumerable: true ā Appears in loops? ā ā
ā ā configurable: true ā Can be deleted? ā ā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Why Property Descriptors Matter:
- ā¢Control whether properties can be modified
- ā¢Hide properties from iteration
- ā¢Prevent property deletion
- ā¢Create computed properties (getters/setters)
- ā¢Implement immutability patterns
Property Descriptor Types
There are two types of property descriptors:
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā Two Types of Descriptors ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā Data Descriptor ā Accessor Descriptor ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā { ā { ā
ā value: any, ā get: function, ā
ā writable: boolean, ā set: function, ā
ā enumerable: boolean, ā enumerable: boolean, ā
ā configurable: boolean ā configurable: boolean ā
ā } ā } ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā Stores a value directly ā Computes value via functions ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā“āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Important: A descriptor cannot be both data and accessor. You cannot have both value/writable AND get/set in the same descriptor.
Getting Property Descriptors
Single Property
const person = {
name: 'Alice',
age: 30,
};
const descriptor = Object.getOwnPropertyDescriptor(person, 'name');
console.log(descriptor);
/*
{
value: "Alice",
writable: true,
enumerable: true,
configurable: true
}
*/
All Properties
const descriptors = Object.getOwnPropertyDescriptors(person);
console.log(descriptors);
/*
{
name: {
value: "Alice",
writable: true,
enumerable: true,
configurable: true
},
age: {
value: 30,
writable: true,
enumerable: true,
configurable: true
}
}
*/
Defining Properties
Object.defineProperty()
const obj = {};
Object.defineProperty(obj, 'id', {
value: 1,
writable: false, // Cannot be changed
enumerable: true, // Shows in loops
configurable: false, // Cannot be deleted or reconfigured
});
obj.id = 2; // Silently fails (or throws in strict mode)
console.log(obj.id); // 1
Object.defineProperties()
const user = {};
Object.defineProperties(user, {
id: {
value: 1,
writable: false,
enumerable: true,
configurable: false,
},
name: {
value: 'Alice',
writable: true,
enumerable: true,
configurable: true,
},
_secret: {
value: 'hidden',
writable: true,
enumerable: false, // Won't appear in Object.keys()
configurable: true,
},
});
Default Descriptor Values
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā Default Values When Defining Properties ā
āāāāāāāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāā¤
ā Attribute ā Object Literal ā defineProperty ā
āāāāāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāā¤
ā value ā as specified ā undefined ā
ā writable ā true ā false ā
ā enumerable ā true ā false ā
ā configurable ā true ā false ā
āāāāāāāāāāāāāāāāāāāāāāāāāā“āāāāāāāāāāāāāāāāāāāāāā“āāāāāāāāāāāāāāāāāāāā¤
ā ā ļø defineProperty defaults are restrictive for safety! ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Configurable Attribute
The configurable attribute controls:
- ā¢Whether the property can be deleted
- ā¢Whether descriptor attributes (except
writable) can be changed - ā¢Whether it can be converted between data/accessor property
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā configurable: true vs false ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā ā
ā configurable: true configurable: false ā
ā āāāāāāāāāāāāāāāāāāāāāā āāāāāāāāāāāāāāāāāāāāāā ā
ā ā ā Can delete prop ā ā ā Cannot delete ā ā
ā ā ā Can change attrs ā ā ā Cannot change ā ā
ā ā ā Can switch to ā ā (except writable ā ā
ā ā accessor/data ā ā true ā false) ā ā
ā āāāāāāāāāāāāāāāāāāāāāā āāāāāāāāāāāāāāāāāāāāāā ā
ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Example
'use strict';
const obj = {};
Object.defineProperty(obj, 'constant', {
value: 42,
writable: false,
configurable: false,
});
// These all fail in strict mode:
obj.constant = 100; // TypeError: Cannot assign
delete obj.constant; // TypeError: Cannot delete
// Cannot reconfigure:
Object.defineProperty(obj, 'constant', {
configurable: true,
}); // TypeError: Cannot redefine
Enumerable Attribute
The enumerable attribute controls whether a property appears in enumeration methods.
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā enumerable: true vs false ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā ā
ā Method true false ā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
ā for...in loop ā included ā skipped ā
ā Object.keys() ā included ā skipped ā
ā Object.values() ā included ā skipped ā
ā Object.entries() ā included ā skipped ā
ā JSON.stringify() ā included ā skipped ā
ā Object.assign() ā copied ā skipped ā
ā spread { ...obj } ā copied ā skipped ā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
ā Object.getOwnPropertyNames() ā included ā included ā
ā Reflect.ownKeys() ā included ā included ā
ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Example
const user = { name: 'Alice' };
Object.defineProperty(user, '_id', {
value: 12345,
enumerable: false,
});
console.log(Object.keys(user)); // ["name"]
console.log(user._id); // 12345 (still accessible!)
console.log(JSON.stringify(user)); // {"name":"Alice"}
// Get ALL properties:
console.log(Object.getOwnPropertyNames(user)); // ["name", "_id"]
Writable Attribute
The writable attribute controls whether the property's value can be changed.
const config = {};
Object.defineProperty(config, 'VERSION', {
value: '1.0.0',
writable: false,
enumerable: true,
configurable: false,
});
config.VERSION = '2.0.0'; // Silently fails (throws in strict mode)
console.log(config.VERSION); // "1.0.0"
Writable and Nested Objects
const obj = {};
Object.defineProperty(obj, 'data', {
value: { x: 1, y: 2 },
writable: false,
});
// Cannot reassign the property
obj.data = { x: 10 }; // Fails
console.log(obj.data); // { x: 1, y: 2 }
// BUT can modify the nested object!
obj.data.x = 100; // Works!
console.log(obj.data); // { x: 100, y: 2 }
Accessor Properties
Accessor properties use get and set functions instead of storing values directly.
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā Accessor Properties ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā ā
ā const person = { ā
ā _firstName: "John", ā
ā _lastName: "Doe", ā
ā ā
ā get fullName() { ā Called when reading ā
ā return `${this._firstName} ${this._lastName}`; ā
ā }, ā
ā ā
ā set fullName(value) { ā Called when writing ā
ā [this._firstName, this._lastName] = value.split(" "); ā
ā } ā
ā }; ā
ā ā
ā console.log(person.fullName); // "John Doe" ā
ā person.fullName = "Jane Smith"; // Triggers setter ā
ā console.log(person._firstName); // "Jane" ā
ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Using defineProperty for Accessors
const circle = {
_radius: 5,
};
Object.defineProperty(circle, 'radius', {
get() {
return this._radius;
},
set(value) {
if (value < 0) {
throw new Error('Radius cannot be negative');
}
this._radius = value;
},
enumerable: true,
configurable: true,
});
Object.defineProperty(circle, 'area', {
get() {
return Math.PI * this._radius ** 2;
},
enumerable: true,
configurable: true,
});
console.log(circle.radius); // 5
console.log(circle.area); // 78.54...
circle.radius = 10;
console.log(circle.area); // 314.15...
Read-Only Accessor (Getter Only)
const user = {
_createdAt: new Date(),
};
Object.defineProperty(user, 'createdAt', {
get() {
return this._createdAt.toISOString();
},
// No setter = read-only
});
console.log(user.createdAt); // "2024-01-15T..."
user.createdAt = new Date(); // Silently fails (throws in strict mode)
Object Freezing and Sealing
JavaScript provides methods to restrict object modification at different levels:
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā Object Protection Levels ā
āāāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā Method ā What it Does ā Check Method ā
āāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā Object.freeze() ā No changes at all ā Object.isFrozen() ā
ā Object.seal() ā No add/remove ā Object.isSealed() ā
ā Object.prevent ā No new properties ā Object.isExtensible() ā
ā Extensions() ā ā (returns false if prev'd) ā
āāāāāāāāāāāāāāāāāāāāāā“āāāāāāāāāāāāāāāāāāāāā“āāāāāāāāāāāāāāāāāāāāāāāāāāāā
Comparison Chart
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā Capabilities After Protection ā
āāāāāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāā¬āāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāā¤
ā Operation ā freeze() ā seal() ā preventExtensions ā
āāāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāā¼āāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāā¤
ā Add properties ā ā ā ā ā ā ā
ā Delete properties ā ā ā ā ā ā ā
ā Change values ā ā ā ā ā ā ā
ā Reconfigure props ā ā ā ā ā ā ā
āāāāāāāāāāāāāāāāāāāāāāāā“āāāāāāāāāāāāāā“āāāāāāāāāāāā“āāāāāāāāāāāāāāāāāāāā
Examples
// Object.freeze() - Complete immutability
const frozen = Object.freeze({
name: 'Frozen',
data: { x: 1 },
});
frozen.name = 'Changed'; // Fails
frozen.new = 'property'; // Fails
delete frozen.name; // Fails
// But nested objects are NOT frozen!
frozen.data.x = 100; // Works!
// Object.seal() - Fixed shape, mutable values
const sealed = Object.seal({
name: 'Sealed',
count: 0,
});
sealed.count = 10; // Works
sealed.new = 'property'; // Fails
delete sealed.name; // Fails
// Object.preventExtensions() - No new properties
const limited = Object.preventExtensions({
name: 'Limited',
});
limited.name = 'Changed'; // Works
limited.new = 'prop'; // Fails
delete limited.name; // Works
Deep Freeze
function deepFreeze(obj) {
// Get all property names
const propNames = Object.getOwnPropertyNames(obj);
// Freeze nested objects first
for (const name of propNames) {
const value = obj[name];
if (value && typeof value === 'object') {
deepFreeze(value);
}
}
return Object.freeze(obj);
}
const deepFrozen = deepFreeze({
name: 'Config',
settings: {
theme: 'dark',
nested: {
value: 42,
},
},
});
deepFrozen.settings.theme = 'light'; // Fails
deepFrozen.settings.nested.value = 0; // Fails
Best Practices
1. Use Getters for Computed Properties
const rectangle = {
width: 10,
height: 5,
get area() {
return this.width * this.height;
},
get perimeter() {
return 2 * (this.width + this.height);
},
};
2. Use Setters for Validation
const product = {
_price: 0,
get price() {
return this._price;
},
set price(value) {
if (typeof value !== 'number' || value < 0) {
throw new Error('Price must be a positive number');
}
this._price = value;
},
};
3. Make Internal Properties Non-Enumerable
function createUser(name) {
const user = { name };
Object.defineProperty(user, '_internalId', {
value: Math.random().toString(36).substr(2, 9),
enumerable: false,
writable: false,
});
return user;
}
const user = createUser('Alice');
console.log(Object.keys(user)); // ["name"]
console.log(user._internalId); // Still accessible if needed
4. Create True Constants
const CONFIG = {};
Object.defineProperty(CONFIG, 'API_URL', {
value: 'https://api.example.com',
writable: false,
enumerable: true,
configurable: false,
});
Object.freeze(CONFIG);
5. Avoid Modifying Built-in Prototypes
// ā Don't do this
Object.defineProperty(Array.prototype, 'customMethod', {
value: function () {},
});
// ā Create utility functions instead
const arrayUtils = {
customMethod(arr) {},
};
Common Use Cases
| Use Case | Solution |
|---|---|
| Constant values | writable: false, configurable: false |
| Hidden properties | enumerable: false |
| Computed properties | Use getters |
| Validated properties | Use setters |
| Immutable objects | Object.freeze() |
| Fixed shape objects | Object.seal() |
| No extension | Object.preventExtensions() |
Files in This Section
- ā¢
README.md- This documentation - ā¢
examples.js- Runnable code examples - ā¢
exercises.js- Practice exercises with solutions
Navigation
- ā¢Previous: 7.2 Object Methods
- ā¢Next: 7.4 Object Static Methods
- ā¢Back to Module 7