JavaScript lets us do a lot of things. It’s sometimes too forgiving in its syntax.
In this article, we’ll look at how to create and use objects, including keeping things private.
Privacy Failures
When we return objects directly within a function that’s supposed to be private, we can modify them directly from the outside.
This is because the variables are passed by reference.
For instance, if we have:
function Foo() {
const bar = {
a: 1,
b: 2
};
// public function
this.getBar = function() {
return bar;
}
}
Then bar
is private, but since it’s returned by getBar
, we can access it from the outside.
We can also modify it.
For instance, if we write:
const foo = new Foo();
bar = foo.getBar();
console.log(foo.getBar());
bar.a = 2;
console.log(foo.getBar());
Then we’ll see that bar
is now {a: 2, b: 2}
.
This isn’t great since we don’t want to mutate bar
from the outside.
Therefore, we should clone the object before returning it or put it in a module.
We can do a shallow clone with the spread operator.
And we can use the export
keyword to export the object in a module.
This way, we either change the clone of the object or get an error if we try to modify it directly.
Object Literals and Privacy
If we want object data to be private, then we’ve to wrap it inside a function and then return parts of it.
For instance, we can write:
(() => {
const name = 'foo';
return {
getName() {
return name;
}
}
})()
getName
has privileged access to the name
constant in the example above.
We ran the function immediately after it’s defined, so we can call it if we assign the returned object to a variable or constant.
Also, we can assign the object to a variable that’s outside of the IIFE as follows:
let obj;
(() => {
const name = 'foo';
obj = {
getName() {
return name;
}
}
})()
Now we can call the obj.getName
after the IIFE has been run.
Prototypes and Privacy
If we don’t want to have separate instances of methods in each instance of a constructor, then we’ve to put them in the prototype.
This way, all instance methods are inherited from the constructor’s prototype.
For instance, we can write:
function Foo() {}
Foo.prototype.hello = function() {
//...
}
However, since constructors have no place to store private data, we’ve to think harder to find a place to store private data.
For instance, we can write:
function Foo() {}
Foo.prototype = (() => {
const foo = "foo";
return {
getFoo() {
return foo;
}
};
})();
In the code above, we have foo
as the private variable.
And we return an object with the getFoo
function.
Therefore, when we call getFoo
as follows:
const foo = new Foo();
console.log(foo.getFoo());
We get 'foo'
logged without exposing the foo
constant itself.
This is one thing that the class syntax doesn’t have right now.
There’s no good way to add private variables with the class syntax, so this is a plus for the constructor syntax.
Revealing Private Functions As Public Methods
If we want to reveal some private functions as public methods, we can do that.
We can use the revelation pattern to reveal some private members of our choice.
For instance, we can write:
const obj = (() => {
const foo = () => {
//...
}
const bar = () => {
//...
}
const baz = () => {
//...
}
return {
foo,
bar
}
})();
We have foo
and bar
returned in the object below, so we can call them with obj.foo()
and obj.bar()
.
This is handy for storing helper functions which we don’t want to expose to the outside.
We can freeze the obj
object to prevent accidental modification.
Module Pattern
The module pattern is where we combined the private and privileged methods and namespaces into one.
For instance, we can define our own module by writing:
const APP = {
utilities: {}
}
APP.utilities.math = (() => {
return {
add(a, b) {
// ...
},
subtract(a, b) {
// ...
}
};
}());
The code above defined a module named APP.utilities.math
.
It has the add
and subtract
methods available to the outside.
We can call them whenever we want to by writing:
APP.utilities.math.add((1, 2)
or:
APP.utilities.math.subtract(1, 2)
We don’t have to return all the members in the returned object.
So we can leave some stuff private.
This is still useful for cases when we don’t have modules in our JavaScript project.
Conclusion
We can create our own modules for cases when we don’t have modules in our project.
To prevent accidental modifying returned objects in functions, we can make a copy of them or freeze them.
We can also have private members in constructor by assigning an IIFE to the prototype
which returns an object.