First things first, what the duck is Closure ? 🦆Wack
I wrote a small example here "How to explain Javascript Closure for 5 years old kid":
So, if you are new to Javascript or never heard or not really understand what the "duck" is Closure, you need to go to back and eat that duck first.
HTML & Pure JS
Let's see a small example from MDN Web Docs
// CSS File
body {
font-family: Helvetica, Arial, sans-serif;
font-size: 12px;
}
h1 {
font-size: 1.5em;
}
h2 {
font-size: 1.2em;
}
// HTML File
<p>Some paragraph text</p>
<h1>some heading 1 text</h1>
<h2>some heading 2 text</h2>
<a href="#" id="size-12">12</a>
<a href="#" id="size-14">14</a>
<a href="#" id="size-16">16</a>
// JS File
function makeSizer(size) {
return function() {
document.body.style.fontSize = size + 'px';
};
}
var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);
document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;
- This code snippet above is how basically closure is applied in web development.
-
size12
,size14
, andsize16
are predefined functions that resize the body text to 12, 14, and 16 pixels, respectively. -
makeSizer
activates "Closure in Javascript" and holds the size for each function. Now we attach them to buttons
In JavaScript, closures are created every time a function is created, at function creation time.
⇒ We have 3 closures are created and each one of them holds their own size.
React
- Continuing with the above example, it will often be used in React as well.
function SizeButtons(props) {
const listSize = props.listSize;
const listItems = listSize.map((size) => {
const handleOnClickItem = () => {
document.body.style.fontSize = size + 'px';
}
return (<button onClick={handleOnClickItem}>{size}</button>);
});
return (
<div>{listItems}</div>
);
}
export default function App() {
return (
<div className="App">
<p>Some paragraph text</p>
<h1>some heading 1 text</h1>
<h2>some heading 2 text</h2>
<SizeButtons listSize={[12, 14, 16]} />
</div>
);
}
- Try it at codesandbox
- So we have an array
listSize
is passed as a prop intoSizeButtons
to render the list of size button. - We use JavaScript
map
to loop through the array and at each item, we return<button>
element for each item. - Finally, we assign the resulting array of elements to
listItems
: - In mental modal,
listItems
looks like this[renderButton12, renderButton14, renderButton16]
, which means 3 functions to render 3 buttons. And in each function, the size value is attached. - So a closure is created every time the callback function is called from
map
- And at this case we have three closures, right !
- This one is similar to the first example, but it is written in a bit different way.
- So let's try to re-write and translate it to exactly similar way to the first example with HTML & Pure JS:
import "./styles.css";
const makeSizeButton = (size) => {
return function() {
document.body.style.fontSize = size + "px";
}
};
function SizeButtons(props) {
const listSize = props.listSize;
const size12 = makeSizeButton(listSize[0]);
const size14 = makeSizeButton(listSize[1]);
const size16 = makeSizeButton(listSize[2]);
const button12 = <button key={12} onClick={size12}>{listSize[0]}</button>
const button14 = <button key={14} onClick={size14}>{listSize[1]}</button>
const button16 = <button key={16} onClick={size16}>{listSize[2]}</button>
const listItems = [button12, button14, button16];
return <div>{listItems}</div>;
}
- Try it at codesandbox
Redux
- This simplified snippet code of Redux
createStore
function, but I will remove a bit its code in order to focus on what we are focusing "Closure"
// createStore.js
function createStore(reducer, initialState) {
var currentReducer = reducer;
var currentState = initialState;
var listeners = [];
var isDispatching = false;
function getState() {
// ...
}
function subscribe(listener) {
// ...
}
function dispatch(action) {
// ...
}
function replaceReducer(nextReducer) {
// ...
}
dispatch({ type: '@@redux/INIT' });
var store = { dispatch, subscribe, getState, replaceReducer };
return store;
}
const store = createStore(...);
Why closure is used here ?
As Redux official document says:
A store holds the whole state tree of your application. The only way to change the state inside it is to dispatch an action on it.
- This means
currentState
ofcreateStore
is inviolable 🏰, it is inaccessible from outside, only insidecreateStore
function can access and update it. - The
createStore
only returns a method to update the state isdispatch
, and of course, it will rule how dispatch works. 🤴 - We— "application developers" have to follow the rule.
- So closure allows us to emulate private methods, and properties inside a function scope, because JavaScript does not provide a native way of doing this like Java, C# or some other class-based programming languages.
How about without Closure ?
// Without closure
function createStore(reducer, initialState) {
const store = {};
store.currentReducer = reducer;
store.currentState = initialState;
store.listeners = [];
store.isDispatching = false;
store.getState = function() {
// ...
};
store.dispatch = function() {
// ...
}
// ...
return store;
}
const store = createStore();
// It allows us to mutate the store
// Which is anti-pattern i
store.currentReducer = null;
So in conclusion:
Closures are useful because they let you associate data (the lexical environment) with a function that operates on that data. This has obvious parallels to object-oriented programming, where objects allow you to associate data (the object's properties) with one or more methods.
Express.js Middleware
- Almost common middleware libraries out there are written in "Style of Closure" . Ex:
cors
,morgan
,compression
- For example: a code snippet inside compression middleware source code
function compression (options) {
var opts = options || {}
// options
var filter = opts.filter || shouldCompress
var threshold = bytes.parse(opts.threshold)
if (threshold == null) {
threshold = 1024
}
return function compression (req, res, next) {
// ....
}
}
- And how it is used in Express app
var express = require('express')
var cors = require('cors')
var app = express()
app.use(compression({
// @TODO: Configure options here
}))
...
- Because middleware needs to store its options through the app's life cycle.
- Whenever Express app call to the middleware, it needs to read the options was initially configured.
In collusion, most of the case, closure is used whenever we need to store something at the run time, such as app global data (Redux), configs, options (Express middleware) in private, or attach data to callback event (React), so that we can access it later when the functions are called to.
“Program to an interface, not an implementation.”
Design Patterns: Elements of Reusable Object Oriented Software
Top comments (4)
Thank you for the post, Trung.
When people learn about closures, knowing how it's used IRL makes it easy to remember and why one should know about it :)
Thank you, it is a method that I am using to learn something and always keeping it in mind when I write blogs.
Surely a good method to retain the knowledge longer~
Quite insightful, thanks !