The Object.defineProperty() method enables granular manipulation of object attributes by defining a new property or modifying an existing one on a specified target object. Unlike standard assignment operations which offer limited flexibility, this approach allows for precise configuration via descriptor objects.
Syntax Overview
Object.defineProperty(targetObj, propKey, descriptorConfig)
targetObj: The object receiving the property modification.propKey: The string key identifying the attribute.descriptorConfig: An object defining the attribute's behavior and characteristics.
Standard assignment usually allows mutable attributes that can be deleted easily. In contrast, using this API restricts mutability based on explicit settings within the descriptor.
Descriptor Categories
There are two distinct types of descriptors available, wich must be defined separately without mixing their properties:
Data Descriptors
These describe properties that store actual values, utilizing value and writable.
const userProfile = {};
Object.defineProperty(userProfile, 'userId', {
value: 50291,
writable: true
});
If specific fields within the descriptor are omitted, they default to undefined (for accessors) or false (for boolean flags like writable, enumerable, and configurable).
Accessor Descriptors
These rely on getter and setter functions to intercept read/write operations instead of storing a static value directly.
const inventory = {};
let stockCount = 0;
Object.defineProperty(inventory, 'stockLevel', {
get: function () {
return stockCount;
},
set: function (amount) {
stockCount = amount;
}
});
inventory.stockLevel = 150;
console.log(inventory.stockLevel); // Outputs: 150
Attribute Configuration Flags
Both descriptor types share common control flags: configurable, enumerable, and writable.
Configurability
When configurable is set to false, the property lifecycle becomes restricted.
- Deletion Block: Attempting to
deletethe property will fail sielntly in non-strict mode, leaving the property intact. - Redefinition Limit: The property cannot be redefined to change other attributes (e.g., switching from data to accessor type).
const systemInfo = {};
Object.defineProperty(systemInfo, 'version', {
value: '1.0.0',
configurable: false
});
delete systemInfo.version;
// Result: version remains present because deletion is blocked
Enumeration
This flag controls whether the property appears in iteration loops like for...in or lists generated by Object.keys().
const metrics = {};
Object.defineProperty(metrics, 'score', {
value: 85,
enumerable: true
});
Object.defineProperty(metrics, 'secretCode', {
value: 'X99',
enumerable: false
});
for (let k in metrics) {
console.log(k); // Logs only 'score'
}
console.log(Object.keys(metrics)); // ['score']
Immutability Settings
Combining writable: false with configurable: false creates a robust constant attribute.
const envConfig = {};
Object.defineProperty(envConfig, 'APP_ID', {
value: 'prod_env_01',
writable: false,
configurable: false
});
envConfig.APP_ID = 'new_value'; // Fails safely
envConfig.NEW_KEY = 'added'; // Allowed if extensible
Object State Management APIs
JavaScript provides utility methods to lock down object states beyond single-property definition.
Prevent Extensions
Prevents the addition of new properties while allowing modifications to existing ones.
const entity = { id: 404 };
Object.preventExtensions(entity);
entity.newFeature = true; // Throws TypeError in strict mode
Sealing
Seals an object, effectively calling preventExtensions and marking all own properties as non-configurable (configurable: false). Values remain modifiable.
const secureData = { token: 'abc' };
Object.seal(secureData);
delete secureData.token; // Blocked
Object.defineProperty(secureData, 'token', { value: 'xyz' }); // Blocked
secureData.token = 'changed'; // Allowed
Freezing
Applies sealing rules and sets all own properties to non-writable (writable: false). This prevents changing the value of existing properties.
const constants = { PI: 3.14 };
Object.freeze(constants);
constants.PI = 22/7; // Throws TypeError in strict mode
Note that freeze applies strictly to immediate properties. Nested objects require recursive freezing to achieve deep immutability.
Semantic Differences: Definition vs Assignment
Understanding how defineProperty differs from dot notation assignment is crucial when dealing with prototype chains.
- Assignment (
obj.prop = val): May trigger a setter found in the prototype chain. It fails if it attempts to overwrite a prototype-only read-only property. - Definition (
Object.defineProperty): Always modifies the object's own property, capable of shadowing or overwriting inherited attributes.
Example scenario:
function Base() {}
Base.prototype.readOnlyProp = 'base_val';
const obj = Object.create(Base.prototype);
// Shadowing via assignment
obj.readOnlyProp = 'own_val';
// Creates own property, masking prototype
// Overriding via definition
Object.defineProperty(obj, 'readOnlyProp', { value: 'forced_own' });
Batch Property Operations
While Object.defineProperty targets a single attribute, Object.defineProperties accepts multiple definitions simultaneously.
const record = {};
Object.defineProperties(record, {
"username": {
value: "admin",
writable: true
},
"role": {
value: "superuser",
writable: false
}
});
console.log(record.role);
This approach is functionally equivalent to iterating through endividual definitions but ensures atomic updates.