Closures In JavaScript
Tags: javascript
Published: 2023-02-08 04:23:47

### Definition from MDN
A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function's scope from an inner function. In JavaScript, closures are created every time a function is created, at function creation time.


#### Playground

```
const sayHi = () => {
    const name = 'Nic'
    function sayIt(){console.log(`Hi, ${name}`)}
    return sayIt
}
const sayWhat = sayHi()
sayWhat() 
```
In above case, what happen when call `sayWhat()`, the result is  `Hi, Nic`.
If a function return something which have some references to this function scope variables, then the variables will be moved to `closure box` instead of collecting by garbage. 
As `sayHi` contain `sayIt`, and `sayIt` is executed when  `sayWhat` was called. Then the variable `name` in the `sayHi` will be removed to closure box as it's used in `sayIt`. when `sayIt` is executed, it will look for the `name` in its scope first, if cannot find it, then go to outer closure box to search. That's how closure works.

```
const sayHi = () => {
	setTimeout(() => {console.log(name)}, 4000)
	const name = 'Oodie'
}
sayHi() 
```
In above case, what happen when call `sayHi()`, the result is  `Oodie`. The explanation is the same as above. 

### What's the benefit from closures
#### Memory Efficient
In below case, every time we call **heavyDuty** function, we need to create a memory to store bigArray, as we call it many times, so it's kind of waste our memory. You may say, we can create big array outside of `heavyDuty` function. But that will pollute the global scope. That's why we apply closure here.
```
function heavyDuty(index){
	const bigArray = new Array(10000).fill('I am a big array')
	console.log('created!')
	return bigArray[index]
}
heavyDuty(8888) // created
heavyDuty(8888) // created
heavyDuty(8888) // created
```
Let apply **closure** to above case. We only need to create big array one time. And we can use it anywhere and anytime.

```
function heavyDutyWithClosure(){
	const bigArray = new Array(10000).fill('silly')
	console.log('created!')
	return function(index){
            // bigArray will always refer to outside bigArray
            return bigArray[index]
        }
}
const getHeavyDuty = heavyDutyWithClosure()
getHeavyDuty(8888)() // created
getHeavyDuty(8888)()
getHeavyDuty(8888)() 
```
#### Encapsulation

```
const makeNuclearButton = () => {
	let timeWithoutDesctruction = 0;
	const passTime = () => timeWithoutDesctruction++;
	const totalPeaceTime = () => timeWithoutDesctruction++;
	const launch = () => {
		timeWithoutDesctruction = -1;
		return "boom";
	};
	setInterval(passTime, 1000);

	return {
		launch: launch,
		totalPeaceTime: totalPeaceTime,
	};
};
const ohno = makeNuclearButton();
console.log(ohno.totalPeaceTime()); // 0
// after 10 second, execute this
console.log(ohno.totalPeaceTime()); // 10
// after 2 second, execute this
console.log(ohno.totalPeaceTime()); // 12, as setInterval is working
```
In above case, you can not change timeWithoutDesctruction, but can get it.
what if we don't want you to get launch function? just remove it from return object.

#### Exercise
Try to convert them by using closures.
Q1
```
let view
function initialize(){
	view = 'boom'
	console.log('view has been set!')
}
initialize() // view has been set!
initialize() // view has been set!
initialize() // view has been set!
```
Result

```
let view
function initialize(){
	let called = 0
	return function(){
		if(called > 0){
			return;
		}else{
			view = 'boom'
			console.log('view has been set!')
			called++
		}
	}

}
const startOnce = initialize() 
startOnce() // view has been set!
startOnce()
startOnce()
```

Q2
```
const array = [1,2,3,4]
for(var i=0; i<array.length; i++){
	setTimeout(function(){console.log('I am at index ' + i)}, 3000)
}
// I am at index 4
// I am at index 4
// I am at index 4
// I am at index 4, as var do not have block scope
```
Result

```
// easy way to fix them by keyword let
const array = [1,2,3,4]
for(let i=0; i<array.length; i++){
	setTimeout(function(){console.log('I am at index ' + i)}, 3000)
}
// I am at index 0
// I am at index 1
// I am at index 2
// I am at index 3
```

```
// easy way to fix them by keyword let
const array = [1,2,3,4]
for(var i=0; i<array.length; i++){
	setTimeout(function(){console.log('I am at index ' + i)}, 3000)
}
// I am at index 0
// I am at index 1
// I am at index 2
// I am at index 3
```

```
// let fix it by using IIFE
const array = [1,2,3,4]
for(var i=0; i<array.length; i++){
  (function(closureI){setTimeout(function() {console.log(closureI)})})(i)
}
// I am at index 0
// I am at index 1
// I am at index 2
// I am at index 3
```
Learn more about the IIFE
{% embed https://dev.to/mavis_chen/javascript-function-types-47p4 %}