One of the biggest challenges of every developer is to maintain code organization.
There is a plethora of the so called "best practices" someone can follow to result in better code.
The revealing module pattern is one of those practices someone can use to achieve better code organization. And it's a pattern that modern features of frontend frameworks are based on. Like React Hooks and Vue Composables.
The Revealing Module Pattern in Javascript
What is the Revealing Module Pattern?
In JavaScript, there are no private
and public
specifiers.
But as you may know, you can achieve the private
effect with closures.
Nice opportunity to remember closures!
A simple example: Let's say we have a function funcInner
encapsulated inside another function funcOuter
alongside some more variables and even methods. Then, if we return only the funcInner
, we eventually have private
staff declared inside the funcOuter
that only live in the scope of this method.
When funcOuter
is executed, it creates a new scope for itself. This scope contains its variables, parameters, and inner functions. When funcInner
is defined, it has access to all the variables and parameters in the scope of funcOuter
, including those that are not passed as arguments to funcInner.
Code Example:
function funcOuter() {
const outerVariable = "I am outer!";
const thisIsInsideFuncOuterScope = "Can't access from outside"
function funcInner() {
const innerVariable = "I am inner!";
console.log(outerVariable);
console.log(innerVariable);
}
return funcInner;
}
const func = funcOuter();
func();
// prints "I am outer!" and "I am inner!"
// We don't have access on `thisIsInsideFuncOuterScope`
// We could also run `funcOutter()()` to immediately run the returned function
The next step:
Using closures, we can actually create modules with private and public methods and variables.
This is what we do in the revealing module pattern.
We use closures to create methods and variables that are hidden from the outside world. All these exist inside a function and we only return
an object that contains only what we want to expose.
This pattern allows us to encapsulate functionality and create modular, reusable code.
Let's see it in action
const myModule = () => {
const privateVar = "Hello outer world";
function privateFunction() {
console.log(privateVar);
}
function publicFunction() {
privateFunction();
}
return {
publicFunction: publicFunction,
};
};
const { publicFunction } = myModule();
publicFunction() // Outputs "Hello outer world"
The publicFunction
is returned as an object from the closure and can be accessed outside the closure.
...A real life example
You can see here how I return only the methods that are responsible only to add and remove the event listeners.
export function touchDeviceHandler(options, callback){
// Private Variables
let startY = 0;
let scrolling = false;
//Private Methods
const onTouchStart = (event: TouchEvent) => {
console.log("onTouchMove");
};
const onTouchMove = (event: TouchEvent) => {
console.log("onTouchMove");
};
const onTouchEnd = () => {
scrolling = false;
};
// We will expose this
const addTouchListeners = () => {
document.addEventListener('touchstart', onTouchStart);
document.addEventListener('touchmove', onTouchMove);
document.addEventListener('touchend', onTouchEnd);
};
// We will expose this too
const removeTouchDeviceListeners = () => {
document.removeEventListener('touchstart', onTouchStart);
document.removeEventListener('touchmove', onTouchMove);
document.removeEventListener('touchend', onTouchEnd);
};
/* Returned Object, containing only what we want to expose */
return {
addTouchListeners,
removeTouchDeviceListeners
};
}
And here you can see how we use touchDeviceHandler
.
I just destructure the returned object
const { addTouchListeners, removeTouchDeviceListeners } = touchDeviceHandler(
options,
fire
);
And eventually, lower in the code, it's so clean and easy for someone to understand what I am actually doing when calling
removeTouchDeviceListeners();
In case you are interested, here is the repo
Eventually
Using the revealing module pattern, the code is so clean and modular that I can even expose addTouchEventListeners();
and removeTouchDeviceListeners();
from the composable. That way, the user of the composable could act on his own style, adding and removing the listeners at his convinience.
Finally
Advantages of the Revealing Module Pattern
- Encapsulation.
- Preventing Variable Name Collisions.
- Modularity.
Thank you for reading up to this line!
Top comments (2)
great
Glad you liked it @muhmmadawd !