It makes it possible for a function to have "private" variables. A closure is a function having access to the parent scope, even after the parent function has closed. - W3School
모든 closure는 아래 3개의 scope를 갖고 있다.
여기서 중요한 점은 모든 outer scope(global scope포함)에 접근할 수 있다는 것이다.
아래 예시는 Mdn에서 가져왔다.
// global scope
var e = 10;
function sum(a){
return function(b){
return function(c){
// outer functions scope
return function(d){
// local scope
return a + b + c + d + e;
}
}
}
}
console.log(sum(1)(2)(3)(4)); // log 20
// You can also write without anonymous functions:
// global scope
var e = 10;
function sum(a){
return function sum2(b){
return function sum3(c){
// outer functions scope
return function sum4(d){
// local scope
return a + b + c + d + e;
}
}
}
}
var sum2 = sum(1);
var sum3 = sum2(2);
var sum4 = sum3(3);
var result = sum4(4);
console.log(result) //log 20
아래처럼 outer scope(callMe) 함수의 변수(count)값을 업데이트 할 수가 있다.
const callMe = () => {
let count = 0;
return () => {
if (count > 0) {
return 'not first';
} else {
count++;
return 'first time';
}
}
}
const init = callMe();
init() // first time
init() // not first
init() // not first
call stack에서 함수가 제거되고 나면 heap memory에 남은 변수들이 저장된다. garbage collecting을 할때, 이 변수들은 closure로 보기때문에, 제거 되지 않는다. 따라서 closure로 만들어진 변수들은 다시 접근할 수 있는 것이다.
const myInfo = (name) => (age) => (location) => console.log(`${name} / ${age} / ${location}`);
const callMyName = myInfo('cho');
const callMyAge = callMyName(20);
closure가 된 변수는 callback 이후에도 변수 위치와 상관없이 접근할 수 있다.
function callback1() {
const hi = 'hi there';
setTimeout(function() {
console.log(hi)
}, 5000)
}
function callback2() {
setTimeout(function() {
console.log(hello)
}, 5000)
const hello = 'hello world';
}
callback1() // hi there
callback2() // hello world
callback1 을 통해서 Web API로 callback되지만, hi라는 변수에 접근할 수 있음을 알 수 있다.
callback2 를 통해서 closure라면 변수 선언 위치는 상관없음을 알 수 있다.
function을 계속해서 생성하는 경우에 closure scope를 사용하여, 재생산에 의한 비효율을 막을 수 있다.
// Bad..
function createAllUser(index) {
const bigArray = Array.from({ length : 1000}, (v, i) => ({ id: i}))
console.log('create all user!');
return bigArray[index];
}
console.log(createAllUser(1)); // create all user, {id: 1}
console.log(createAllUser(20)); // create all user, {id: 20}
console.log(createAllUser(100)); // create all user, {id: 100}
// Good!!
function createAllUser() {
const bigArray = Array.from({ length : 1000}, (v, i) => ({ id: i}))
console.log('create all user!');
return function(index) {
return bigArray[index];
}
}
const users = createAllUser(); // create all user!
console.log(users(1)); // { id: 1 }
console.log(users(20)); // { id: 20 }
console.log(users(100)); // { id: 100 }
closure를 사용하지 않는경우, 함수 호출마다 bigArray를 생성하게 된다. 반면 closure를 사용한 경우는 closure 생성시에만 bigArray를 생성하고, 그 후에는 closure내에 있는 bigArray를 재사용하게 된다.
내 API를 필요에 따라 숨길 수 있다.
const makeNuclearButton = () => {
let timeWithoutDestruction = 0;
const passTime = () => timeWithoutDestruction++;
const totalPeaceTime = () => timeWithoutDestruction;
const launch = () => {
timeWithoutDestruction = -1;
return '💥';
}
setInterval(passTime, 1000);
return {
totalPeaceTime,
}
}
const ohno = makeNuclearButton();
ohno.passTime() // ohno에는 return object에는 launch가 없다.
원하는 결과
index 0 value is a
index 1 value is b
index 2 value is c
index 3 value is d
const arr = ['a', 'b', 'c', 'd'];
// 모든 결과가 index 4 value is undefined로 나온다. (같은 global variable i를 참조하기 때문에)
for (var i = 0; i < arr.length; i++) {
setTimeout(() => {
console.log(`index ${i} value is ${arr[i]}`)
})
}
// let을 사용하면 block scope가 생기므로, 원하는 결과를 얻는다.
for (let i = 0; i < arr.length; i++) {
setTimeout(() => {
console.log(`index ${i} value is ${arr[i]}`)
})
}
// IIFE을 사용하면, i가 function의 local 변수로 저장되기 때문에, 원하는 결과를 얻는다. (IIFE가 closure 환경을 만드는 것이다.)
for (var i = 0; i < arr.length; i++) {
(function (closureI) {
setTimeout(() => {
console.log(`index ${closureI} value is ${arr[closureI]}`)
})
})(i)
}