Method 1: Using Object Initializer Syntax
Define getters and setters during object creation using literal syntax:
(function () {
const obj = {
value: 7,
get computedValue() {
return this.value + 1;
},
set multiplier(newValue) {
this.value = newValue / 2;
}
};
console.log(obj.value); // 7
console.log(obj.computedValue); // 8
obj.multiplier = 50;
console.log(obj.value); // 25
})();
In Chrome debugging, you'll notice the object now includes special getter and setter properties. The output shows how these methods allow indirect modification of internal properteis.
A common question arises: Can we name the getter/setter methods the same as the internal property? Attempting this creates infinite recursion:
(function () {
const obj = {
value: 7,
get value() { // This causes infinite recursion
return this.value + 1;
},
set value(newValue) { // This also causes infinite recursion
this.value = newValue / 2;
}
};
// This will cause maximum call stack error
// console.log(obj.value);
})();
When accessing obj.value, it calls the getter method, which again accesses this.value, creating an endless loop that results in a stack overflow error.
ES6 Syntax (currently supported primarily in Firefox):
(function () {
const propName1 = "getterKey";
const propName2 = "setterKey";
const obj = {
value: 7,
get [propName1]() {
return this.value + 1;
},
set [propName2](newValue) {
this.value = newValue / 2;
}
};
console.log(obj.value); // 7
console.log(obj[propName1]); // 8
obj[propName2] = 50;
console.log(obj.value); // 25
})();
Method 2: Using Object.create()
The Object.create() method creates objects with specified prototypes and properties:
(function () {
const obj = Object.create(
Object.prototype, // prototype
{
internalProperty: {
get: function() {
return this._data || 42;
},
set: function(value) {
this._data = value * 2;
},
enumerable: true,
configurable: true
}
}
);
console.log(obj.internalProperty); // 42 (default)
obj.internalProperty = 10;
console.log(obj.internalProperty); // 20
})();
The second parameter accepts a property descriptor object where each property's configuration includes data descriptors or accessor descriptors. Accessor descriptors enable adding getter and setter methods to newly created objects.
Method 3: Using Object.defineProperty()
Direct define individual properties with specific descriptors:
(function () {
const obj = {};
let internalValue = 10;
Object.defineProperty(obj, 'calculated', {
get: function() {
return internalValue * 3;
},
set: function(newValue) {
internalValue = newValue;
},
enumerable: true,
configurable: true
});
console.log(obj.calculated); // 30
obj.calculated = 5;
console.log(obj.calculated); // 15
})();
Method 4: Using Object.defineProperties()
Define multiple properties at once:
(function () {
const obj = {};
let counter = 0;
Object.defineProperties(obj, {
increment: {
get: function() {
return ++counter;
},
enumerable: true
},
currentCount: {
get: function() {
return counter;
},
set: function(value) {
counter = value;
},
enumerable: true
}
});
console.log(obj.increment); // 1
console.log(obj.currentCount); // 1
obj.currentCount = 5;
console.log(obj.currentCount); // 5
})();
Method 5: Using Class Syntax (ES6+)
Modern approach using class declarations:
(function () {
class DataContainer {
constructor(initialValue = 0) {
this._storage = initialValue;
}
get processedData() {
return this._storage * 2 + 1;
}
set processedData(value) {
this._storage = (value - 1) / 2;
}
}
const container = new DataContainer(5);
console.log(container.processedData); // 11
container.processedData = 15;
console.log(container._storage); // 7
})();
Each method offers different advantages depending on when and how you need to add getters and setters to you're JavaScript objects.