The Object-Oriented Paradigm made a huge shake in the way developers think and write code even if you don't like it or don't like its premises. This not so new paradigm of encapsulating data and procedures in form of attributes and methods influenced a lot of recent languages even if some of them are not using it as its main paradigm.
Languages like: C++, Java, Python, and even JavaScript are considered and declared languages that implement the Object-Oriented Paradigm (OOP). As will be discussed, JavaScript has its own way of dealing with Objects with some specificities. But first, there is a starting point that should be discussed: there's one essential fact in JavaScript that goes in the wrong direction of OOP: non-existence of encapsulation.
There's a repo with some tests too! Check it out!
Classes and Objects
Classes are definitions of data-types: what data they will store/hide and how it should behave. An instance of one Class will be able to execute functions as methods and store data as attributes. Those instances are the so-called Objects that lives inside the runtime execution of a program.
One of the important features in OOP is the fact that the Objects should have the ability to encapsulate(hide) its data. That means that if someone tries accessing some information from the Object, it should not be able to do it if the Class says so. Consider the following example:
Let's say that Elon Musk 🧑💼 created an awesome Trash Can
that can perform 3 simple tasks:
- Throw one 'junk' item into the Trash Can
- Clean all items inside the Trash Can, all at once
- One button that shows at the display if the Trash Can is fully empty or not
The interface for that TrashCan
is something like:
TrashCan {
throwAway(item);
clean();
isEmpty();
}
As JavaScript has the class
keyword, it's possible to consider one implementation of this Trash
as the following
class TrashCan {
constructor() {
this.items = [];
}
throwAway(item) {
this.items = [...this.items, item];
}
clean() {
this.items = [];
}
isEmpty() {
return this.items.length === 0;
}
}
var elonTrashCan = new TrashCan();
Now the elonTrashCan
is empty and ready to start doing its work. But what happens with the execution of
elonTrashCan.throwAway('paper ball');
elonTrashCan.throwAway('empty Starbucks cup of coffee');
elonTrashCan.throwAway('empty package of Cookies');
elonTrashCan.clean();
elonTrashCan.items = ['SpaceX secret project'];
console.log(elonTrashCan.isEmpty()); // --> ???
- Elon Musk 🧑💼 will be mad at us that we broke his Trash Can
-
elonTrashCan.isEmpty()
will returnfalse
, because we definedelonTrashCan.items
with 1 item inside -
elonTrashCan.items
is not accessible, so theelonTrashCan.isEmpty()
call will returnstrue
The answer is option 2. It is possible to access items
inside the Object instance even without explicit items
declaration outside constructor
.
Using that example and considering an ideal Object-Oriented language implementation, the execution of elonTrashCan.items
should result in an error of the program trying to access a private attribute. In JavaScript, these calls are possible, valid ones, and results in no error.
So, isn't it possible to create Privacy in JavaScript? Is there a way of hiding data from outside the Object and exposing just public
data?
Module Pattern
The good news is there's one behavior in JavaScript that provides something related to privacy: Closures. There's this post written about Closures in case of interest
Using Closures for hiding variables and functions is a good approach for encapsulate data inside one instance and just expose the desired interface of it.
But how do actually this works?
Let's create the same Elon Musk 🧑💼 TrashCan
Object writing a Function and returning just the public interface of it as the following code
const TrashCan = () => {
let items = [];
const throwAway = item => {
items = [...items, item];
}
const clean = () => {
items = [];
}
const isEmpty = () => {
return items.length === 0;
}
return {
throwAway,
clean,
isEmpty,
}
}
var elonTrashCan = TrashCan();
And for the new elonTrashCan
let's try to execute the same code as above
elonTrashCan.throwAway('paper ball');
elonTrashCan.throwAway('empty Starbucks cup of coffee');
elonTrashCan.throwAway('empty package of Cookies');
elonTrashCan.clean();
elonTrashCan.items = ['SpaceX secret project'];
console.log(elonTrashCan.isEmpty()); // --> ???
- Elon Musk 🧑💼 will be even madder at us that we broke his second Trash Can
-
elonTrashCan.isEmpty()
will returnfalse
, because we again definedelonTrashCan.items
with 1 item inside -
elonTrashCan.items
is not accessible, so theelonTrashCan.isEmpty()
call will returnstrue
Actually something very strange happens:
-
elonTrashCan.isEmpty()
returnsfalse
because our internalitems
are empty -
elonTrashCan.items
has 1 item in it
Using this approach, it is possible to 'limit' the outside world on accessing just the desired interface and has hidden content inside it. On the other hand, JavaScript lets the definition of new properties in runtime execution, even with the same names used by its Closure.
The code inside of the Closure will not depend on those new properties, as the original ones are stored inside that Closure, unaccessible. And, the original mission is now accomplished: Privacy. The Module Pattern is viable for attributes and can be used for hiding methods too.
For the side-effect with the creation of new properties, it's hardly suggested not to change the original interface and even make some tests before using those properties like
if(typeof elonTrashCan.items === 'undefined') {
console.log('No exposed items!') // --> No exposed items!
}
Wrap up
After some discussion about the Object-Oriented Paradigm and the Class implementation of JavaScript, maybe the JS Class is not the best choice for creating Objects with private data if you are not using a transpiler like Babel.
Using Closures and the Module Pattern it is possible to accomplish Privacy in JavaScript in a simple and reusable way. If the class
implementation is inevitable, consider using a transpiler or a more robust approach with some use of the Module Pattern. The use of transpilers is hardly incouraged!
Even with some apparent losses like inheritance
, there are still valid ways of implementing that benefits even with Modules.
Did I miss something? Is there a thing that you think it's not clear? Feel free to reach me at the comment section or by message and discuss it!
Top comments (5)
"Actually something very strange happens:
elonTrashCan.isEmpty() returns false because our internal items are empty"
Is it me or elonTrashCan.isEmpty() returns true?
Hey! Thanks for your reply!
Actually,
elonTrashCan.isEmpty()
returnsfalse
asTrashCan
is a Module and not a Class. You can read more about it at the related and cited post about ClosuresYou could also run the test code and see it running.
It's not you :), I think it's just an error, should be:
"3 - elonTrashCan.items is accessible, so the elonTrashCan.isEmpty() call will returns FALSE"
Class properties are public by default. developer.mozilla.org/en-US/docs/W...
Closure doesn't expose 'items', but class does and this is the big difference explained in the article.
As I mentioned in another reply, I'm not actually using
Classes
. What you said is correct forClass
😄🔝