The Getters and Setters in JavaScript are known as accessor properties. They look like normal properties but are actually functions mapped to a Property. We use “get” to define a getter method and “set” to define a setter method. The Setter method runs when we assign a value to the Property. The Getter method runs when we access the Property.
Table of Contents
What are Getters & Setters in JavaScript
The Getters and setters are known as Accessor Properties in JavaScript.
We learned about JavaScript Properties. Property is a variable attached to the JavaScript object. We can assign value to the Property or retrieve its value at any time.
Properties are classified based on how we assign value to them or retrieve value from them. There are two kinds of properties in JavaScript. They are
- Data Properties
- Accessor Properties.
The Data Property is mapped to a value. The value can be a primitive value, object, or function. In the following example, firstName
, lastName
& age
are data properties. We assign or retrieve the values from these properties directly.
1 2 3 4 5 6 7 8 9 10 | var person = { firstName:"", lastName:"", age: 0 }; person.firstName="Allie" // => Assign a value console.log(person.firstName) //Allie => Retrieve a value |
The accessor property is not mapped to a value but to a function. We call this function as accessor function. It is the job of the accessor function to store or retrieve the value.
The accessor function that retrieves the value of a property is known as the Getter method. We use the Get keyword to declare a Getter method.
The accessor function that assigns a value to a property is known as the Setter method. We use the Set keyword to declare a Setter method.
The Getter and Setter methods are useful when we do not want to access the property directly.
How to Create Getters & Setters
There are two ways to create Getter & Setter properties in JavaScript.
- Object Literal
- DefineProperties Method
There is an older syntax, __defineGetter__
and __defineSetter__
, but it’s deprecated
Using Object Literal
The following is the syntax for creating a Getter & Setter property using the object Literal syntax or object initializer.
JavaScript Getter
We use the get keyword followed by a function expression. The name of the function becomes the name of the property ( propName
in the above example).
The following is the syntax of the getter method.
1 2 3 4 5 6 7 8 9 | let obj = { get propName() { // getter method // this code is executed when we access the property using // Example value = obj.propName }, } |
The getter function executes when we read the value of the Property. Note that we cannot pass an argument to the getter method. The return value of the getter method becomes the value of the property access expression.
In the following example, color is a getter property, while _color is a regular Property. The color function returns the value of the _color property.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | var car = { _color: "blue", // Accessor Property with the name color get color() { //getter return this._color; }, }; console.log(car.color) |
We access the color getter property just like any other Javascript Property. i.e. using the dot notation
1 2 3 | console.log(car.color) |
Note that although the color is a function, we do not invoke it like a function i.e car.color()
. But access it just like a property i.e. car.color
Javascript Setter
To create a setter method, we use the set keyword followed by a function expression. The name of the function becomes the name of the property ( propName
in the above example).
The following is the syntax of the setter method.
1 2 3 4 5 6 7 8 9 10 | let obj = { set propName(value) { // setter method // the code is executed when we assign a value to the property // Example obj.propName = value } }; |
The setter
method executes when we assign a value to the property. JavaScript invokes the setter method with the value of the right-hand side of the assignment as the argument. If the setter method returns a value then it is ignored.
In the following example, color is a setter property, while _color is a regular Property. The color function accepts a value and updates the _color property.
1 2 3 4 5 6 7 8 9 10 11 | var car = { _color: "blue", set color(value) { //setter this._color=value; } }; car.color="red"; |
We assign a new value to the color setter property just like any other Javascript Property. i.e. using the assignment.
In the code car.color="red";
the assignment value (i.e. red in the example) is passed as the argument to the setter
method. Not that we do not invoke the setter property as car.color("red")
but as car.color="red"
.
Javascript Getter & Setter
The combined syntax for creating both setter and getter is as shown below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | let obj = { get propName() { // getter method // this code is executed when we access the property using // Example value = obj.propName }, set propName(value) { // setter method // the code is executed when we assign a value to the property // Example obj.propName = value } }; |
The example code using both getter & Setter is as follows
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | var car = { //Regular Property //Also known as backing Property to color getter & setter property _color: "blue", // Accessor Property with the name color get color() { //getter return this._color; }, set color(value) { //setter this._color=value; } }; //Using the getter method console.log(car.color); //blue //Setting color. Runs the setter method car.color="red"; console.log(car.color); //red Accessing the property //You can also access the backing property console.log(car._color); //red |
When we access the car.color
the getter method executes and it returns the value of the _color
property.
We assign the car.color="red"
the setter method executes. Inside the setter method, we assign the value to the property _color
.
Notice that the color
accessor property behind the scene uses the _color
property to store the value. _color
property is the backing property of the color
accessor property. As a convention, we prepend the backing property with an underscore to indicate that _color
should not be accessed directly.
Note that you cannot use the same name for an accessor property and a regular property.
Since ES6, you can also use the computed property names in getters & setters also. Simply enclose the expression inside square brackets( []
).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | colorVar="color" var car = { _color: "blue", get [colorVar]() { return this._color; }, set [colorVar](value) { this._color=value; } }; console.log(car.color); //blue car.color="red"; console.log(car.color); //red |
Using DefineProperties
We can add a getter & setter on existing objects using the defineProperty function.
The definePropery not only allows us to add a new property but also allows us to set its descriptors ( known as accessor descriptors) or flags. The accessor descriptors have the following properties.
get
is a function without arguments that serves as a getter for the property.
set
is a function with one argument that serves as a setter for the property.
enumerable
if true, then this property shows up during the enumeration of the properties. Defaults to false.
configurable
if true, then we are allowed to modify the property descriptor and delete the property. Defaults to false.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | var car = { _color: "blue", }; Object.defineProperty(car, 'color', { get: function() { return this._color }, set: function(value) { this._color = value }, enumerable: true, configurable: true, }) console.log(car.color); car.color="red"; console.log(car.color); console.log(car._color); |
Using Getters & Setters
Use getter
or setter
only when you need a specific functionality provided by them.
There is no need to use accessor methods if you are simply using them to get or set the data property as in the example below. Plain property access is a better option here.
1 2 3 4 5 6 7 8 9 10 11 | const person = { _name: "", get name() { return this._name; }, set name(value) { this._name = value; }, }; |
But there are some use cases, where you can think of using the Getters & Setters.
Validating values
In the example below, we want to limit the rate field to 10, if the user assigns any rate above 10. This can be easily achieved by a setter method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | var IntCalculator = { _rate: 0, get rate() { return this._rate; }, set rate(value) { if (value > 10) { console.log("Invalid Rate"); value = 10; } else if (value < 0) { console.log("Invalid Rate"); value = 0; } this._rate = value; }, }; IntCalculator.rate = 5; console.log(IntCalculator.rate); //5 IntCalculator.rate = 10; console.log(IntCalculator.rate); //10 IntCalculator.rate = 100; console.log(IntCalculator.rate); //10 IntCalculator.rate = -1; console.log(IntCalculator.rate); //0 |
Another use case is to log the values as the property is being read or written.
Read-only /Write-only Properties
We can use the Setters & getters to create read-only or write-only properties.
If the property has only a getter method, then it is a read-only property. If it has only a setter method then it is a write-only property
In the following example, we only define a get method, making the color property read-only. Setting the color property using an assignment ( car.color="red";
) will not throw the error, but won’t do anything.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | var car = { _color: "blue", get color() { return this._color; }, }; console.log(car.color); //blue //Setting color. But it wont work //as it is read only. No error is thrown car.color="red"; console.log(car.color); //blue |
Similarly, you can create a write-only property by only defining the setter method. Any attempt to read the write-only property always returns undefined.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | var car = { _color: "blue", set color(value) { this._color=value }, }; //Colod is write only. Hnece always returns undefined console.log(car.color); //undefined car.color="red"; console.log(car.color); //undefined console.log(car._color); //red |
Perform Some logic
In the following IntCalculator, we use the setter method to check if the rate & amount differs from their previous values and calculate the interest if they differ. This helps in saving precious CPU time.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | var IntCalculator = { _amount: 0, _rate: 0, _int: 0, get amount() { return this._amount; }, set amount(value) { if (this._amount != value) { this._amount = value; this._calcInt(); } }, get rate() { return this._rate; }, set rate(value) { if (this._rate != value) { this._rate = value; this._calcInt(); } }, _calcInt() { this._int = (this._amount * this._rate) / 100; console.log("int calculated") }, get int() { return this._int; }, }; IntCalculator.amount=1000; IntCalculator.rate=10; console.log(IntCalculator.int) //Interest is not calculated here IntCalculator.amount=1000; |
We can further optimize the above code, by running the calculation only when the user asks for interest. The code runs the calculations only if the amount & rate differs by checking the _isDirty
variable.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | var IntCalculator = { _amount: 0, _rate: 0, _int: 0, _isDirty:true, get amount() { return this._amount; }, set amount(value) { if (this._amount != value) { this._amount = value; this_isDirty=true; } }, get rate() { return this._rate; }, set rate(value) { if (this._rate != value) { this._rate = value; this_isDirty=true; } }, _calcInt() { this._int = (this._amount * this._rate) / 100; console.log("int calculated") }, get int() { if (this._isDirty ) { this._calcInt() this._isDirty=false } return this._int; }, }; IntCalculator.amount=1000; IntCalculator.rate=10; //Interest is now calcuated console.log(IntCalculator.int) IntCalculator.amount=1000; //Not calculated console.log(IntCalculator.int) |
Pitfalls
Backing Property
We use the underscore in the property names to indicate that nobody should access these variables.
In the Intcalculator example, if anyone changes the _amount
or _rate
directly, then our calculator will not calculate the interest correctly and fails. This is because, in JavaScript, you cannot create a private property like in C# or Java.
One simple pattern to avoid such an issue is use the enclose the object in a function and use the concepts of block scope & lexical scope
In the example below, we move the car object inside the function getCar and return it. We move the private property _color outside the car object but inside the function. Due to the rules of block scope, the _color is accessible inside the car object, but not accessible outside the getCar function.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | function getCar() { let _color= "blue" //block scoped.out side the object let car = { get color() { return _color; }, set color(value) { _color=value; } }; return car //returns the object } car = getCar() //get the object from the function console.log(car.color); //blue car.color="red"; console.log(car.color); //red console.log(car._color); //undefined // This will create a new property in car object // But will not affect the color property car._color="green" console.log(car.color); //red console.log(car._color); //green. |
Note that when we assign green to the car._color, it creates a new property _color in the car object. This does not affect the color property as it depends on the _color property defined in the function scope.
Getter has higher precedence
You should not give the same name to a getter and a property.
This is because the getter will always take precedence
In the example below, you will never be able to access name
property as the getter property name
has higher precedence.
1 2 3 4 5 6 7 8 9 10 | const person = { name: 'Bill Gates', get name() { return 'Jeff Bezos'; } }; console.log(person.name) //Jeff Bezos |
If you happen to use the name as the backing property, you will end up with a ” Maximum call stack size exceeded” error as in the example below. this.name
 inside the getter function resolves to itself hence creating an infinite loop
1 2 3 4 5 6 7 8 9 10 11 | const person = { name: 'Bill Gates', get name() { return this.name; } }; console.log(person.name) //Uncaught RangeError: Maximum call stack size exceeded |
getValue / setValue pattern
You can implement functionality similar to getter & setter using the getValue / setValue pattern.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | var car = { _color: "blue", getColor() { return this._color; }, setColor(value) { this._color=value; } }; //Getting Color console.log(car.getColor()); //blue //Setting Color car.setColor("red"); console.log(car.getColor()); //red console.log(car._color); //red |
Read More