JavaScript Getters and Setters: A Developer’s Guide
Getters and setters are functions or methods used to get and set the values of variables. The getter-setter concept is common in computer programming: almost all high-level programming languages come with a set of syntax to implement getters and setters, including JavaScipt.
Read Also: 4 Useful JavaScript Statements You Should Know
In this post, we’ll see what getters setters are, and how to create and use them in JavaScript.
Getters-setters and encapsulation
The idea of getters and setters is always mentioned in conjunction with encapsulation. Encapsulation can be understood in two ways.
Firstly, it is the setting up of the data–getters–setters trio to access and modify that data. This definition is useful when some operations, such as validation, have to be performed on the data before saving or viewing it—getters and setters provide the perfect home for it.
Secondly, there’s a stricter definition according to which encapsulation is done to hide data, to make it inaccessible from other code, except through the getters and setters. This way we don’t end up accidentally overwriting important data with some other code in the program.
Create getters and setters
1. With methods
Since getters and setters are basically functions that fetch/change a value, there are more than one ways to create and use them. The first way is:
var obj = { foo: 'this is the value of foo', getFoo: function() { return this.foo; }, setFoo: function(val) { this.foo = val; } } console.log(obj.getFoo()); // "this is the value of foo" obj.setFoo('hello'); console.log(obj.getFoo()); // "hello"
This is the simplest way to create getters and setters. There’s a property foo
and there are two methods: getFoo
and setFoo
to return and assign a value to that property.
2. With keywords
A more “official” and robust way of creating getters and setters is by using the get
and set
keywords.
To create a getter, place the get
keyword in front of a function declaration that will serve as the getter method, and use the set
keyword in the same way to create a setter. The syntax is as follows:
var obj = { fooVal: 'this is the value of foo', get foo() { return this.fooVal; }, set foo(val) { this.fooVal = val; } } console.log(obj.foo); // "this is the value of foo" obj.foo = 'hello'; console.log(obj.foo); // "hello"
Note that the data can only be stored under a property name (fooVal
) that’s different from the name of the getter-setter methods (foo
) because a property holding the getter-setter can’t hold the data as well.
Which way is better?
If you choose to create getters and setters with keywords, you can use the assignment operator to set the data and the dot operator to get the data, the same way you’d access/set the value of a regular property.
However, if you choose the first way of coding getters and setters, you have to call the setter and getter methods using the function call syntax because they are typical functions (nothing special like those created using the get
and set
keywords).
Also, there’s a chance you might end up accidentally assigning some other value to the properties that held those getter-setter methods and lose them completely! Something you don’t have to worry about in the later method.
So, you can see why I said the second technique is more robust.
Overwrite prevention
If for some reason you prefer the first technique, make the properties holding the getter-setter methods read-only by creating them using Object.defineProperties
. Properties created via Object.defineProperties
, Object.defineProperty
and Reflect.defineProperty
automatically configure to writable: false
which means read-only:
/* Overwrite prevention */ var obj = { foo: 'this is the value of foo' }; Object.defineProperties(obj, { 'getFoo': { value: function () { return this.foo; } }, 'setFoo': { value: function (val) { this.foo = val; } } }); obj.getFoo = 66; // getFoo is not going to be overwritten! console.log(obj.getFoo()); // "this is the value of foo"
Operations inside getters and setters
Once you’ve introduced the getters and setters, you can go ahead and perform operations on the data before changing or returning it.
In the code below, in the getter function the data is concatenated with a string before being returned, and in the setter function a validation of whether the value is a number or not is performed before updating n
.
var obj = { n: 67, get id() { return 'The ID is: ' + this.n; }, set id(val) { if (typeof val === 'number') this.n = val; } } console.log(obj.id); // "The ID is: 67" obj.id = 893; console.log(obj.id); // "The ID is: 893" obj.id = 'hello'; console.log(obj.id); // "The ID is: 893"
Protect data with getters and setters
So far, we covered the use of getters and setters in the first context of encapsulation. Let’s move on to the second, i.e. how to hide data from outside code with the help of getters and setters.
Unprotected data
The setting up of getters and setters doesn’t mean the data can only be accessed and changed via those methods. In the following example, it’s changed directly without touching the getter and setter methods:
var obj = { fooVal: 'this is the value of foo', get foo() { return this.fooVal; }, set foo(val) { this.fooVal = val; } } obj.fooVal = 'hello'; console.log(obj.foo); // "hello"
We didn’t use the setter but directly changed the data (fooVal
). The data we initially set inside obj
is gone now! To prevent this from happening (accidentally), you need some protection for your data. You can add that by limiting the scope of where your data is available. You can do that by either block scoping or function scoping.
1. Block scoping
One way is to use a block scope inside which the data will be defined using the let
keyword which limits its scope to that block.
A block scope can be created by placing your code inside a pair of curly braces. Whenever you create a block scope make sure to leave a comment above it asking for the braces to be left alone, so that no one removes the braces by mistake thinking they are some extra redundant braces in the code or add a label to the block scope.
/* BLOCK SCOPE, leave the braces alone! */ { let fooVal = 'this is the value of foo'; var obj = { get foo() { return fooVal; }, set foo(val) { fooVal = val } } } fooVal = 'hello'; // not going to affect the fooVal inside the block console.log(obj.foo); // "this is the value of foo"
Changing/creating fooVal
outside the block won’t affect the fooVal
referred inside the getters setters.
2. Function scoping
The more common way to protect the data with scoping is by keeping the data inside a function and returning an object with the getters and setters from that function.
function myobj(){ var fooVal = 'this is the value of foo'; return { get foo() { return fooVal; }, set foo(val) { fooVal = val } } } fooVal = 'hello'; // not going to affect our original fooVal var obj = myobj(); console.log(obj.foo); // "this is the value of foo"
The object (with the foo()
getter-setter inside of it) returned by the myobj()
function is saved in obj
, and then obj
is used to call the getter and setter.
3. Data protection without scoping
There’s also another way you can protect your data from being overwritten without limiting its scope. The logic behind it goes like this: how can you change a piece of data if you don’t know what is called?
If the data has a not so easily reproducible variable/property name, chances are no one (even ourselves) is going to end up overwriting it by assigning some value to that variable/property name.
var obj = { s89274934764: 'this is the value of foo', get foo() { return this.s89274934764; }, set foo(val) { this.s89274934764 = val; } } console.log(obj.foo); // "this is the value of foo"
See, that’s one way of working things out. Although the name I chose is not a really good one, you can also use random values or symbols to create property names as it’s proposed by Derick Bailey in this blog post. The main goal is to keep the data hidden from other code and let a getter-setter pair to access/update it.
When should you use getters and setters?
Now comes the big question: do you start assigning getters and setters to all your data now?
If you’re hiding data, then there’s no other choice.
But if your data being seen by other code is fine, do you still need to use getters setters just to bundle it up with code that performs some operations on it? I’d say yes. Code adds up very soon. Creating micro units of individual data with its own getter-setter provides you with a certain independence to work on said data without affecting other parts of the code.
Read Also: 10 Reasons Why You Need Code Optimization