Object.defineProperty()
ES5+Defines a new property directly on an object, or modifies an existing property.
Syntax
Object.defineProperty(obj, prop, descriptor)Parameters
obj Object The object on which to define the property
prop string The name of the property to define
descriptor Object The descriptor for the property being defined
Return Value
The object that was passed to the function
Examples
const obj = {};
Object.defineProperty(obj, 'x', {
value: 42,
writable: false
});
console.log(obj.x);
obj.x = 100;
console.log(obj.x); 📌 When to Use
Use Object.defineProperty() when you need fine-grained control over property behavior, implementing getters and setters, creating non-enumerable or non-writable properties, or building reactive data systems. Essential for library authors and advanced metaprogramming.
⚠️ Common Mistakes
Forgetting default values - all boolean descriptors (writable, enumerable, configurable) default to false when using defineProperty.
Mixing value/writable with get/set - you cannot define both a value and accessors on the same property.
Not setting configurable: true when you might need to redefine the property later - once false, it cannot be changed.
✅ Best Practices
Use computed property getters for derived values: defineProperty(obj, "fullName", { get() { return this.first + " " + this.last; } })
Create non-enumerable methods to avoid them appearing in for...in loops or Object.keys().
Consider using ES6 class getters/setters for simpler cases - they are more readable than defineProperty.
⚡ Performance Notes
Properties defined with accessors (get/set) are slower than regular properties because they invoke function calls. However, this overhead is minimal in most cases. Defining many properties at once with Object.defineProperties() is more efficient than multiple defineProperty() calls. Frozen or sealed objects with defined properties have optimized access paths in modern engines.
🌍 Real World Example
Reactive Data Binding System
Create a simple reactive system using Object.defineProperty() to automatically track and respond to property changes.
function reactive(target) {
const listeners = {};
const values = { ...target };
Object.keys(target).forEach(key => {
listeners[key] = [];
Object.defineProperty(target, key, {
get() {
console.log('Getting ' + key + ': ' + values[key]);
return values[key];
},
set(newValue) {
const oldValue = values[key];
if (oldValue !== newValue) {
values[key] = newValue;
console.log('Setting ' + key + ': ' + oldValue + ' -> ' + newValue);
listeners[key].forEach(fn => fn(newValue, oldValue));
}
},
enumerable: true,
configurable: true
});
});
// Add watch method (non-enumerable)
Object.defineProperty(target, '$watch', {
value: function(key, callback) {
if (listeners[key]) {
listeners[key].push(callback);
}
},
enumerable: false
});
return target;
}
// Usage
const user = reactive({
name: 'John',
age: 25
});
// Watch for changes
user.$watch('age', (newVal, oldVal) => {
console.log('Age changed from ' + oldVal + ' to ' + newVal);
});
user.name; // "Getting name: John"
user.age = 26; // "Setting age: 25 -> 26", "Age changed from 25 to 26"
// $watch is not enumerable
console.log(Object.keys(user)); // ['name', 'age']