클로저란?
두 개의 함수로 만들어진 환경으로 특별한 객체의 한 종류
-> 외부 함수 호출이 종료되더라도 외부 함수의 지역 변수 및 변수 스코프 객체의 체인 관계를 유지할 수 있는 구조를 클로저라고 한다.
++ 클로저는 반환된 내부함수가 자신이 선언됐을 때의 환경(Lexical environment)인 스코프를 기억하여 자신이 선언됐을 때의 환경(스코프) 밖에서 호출되어도 그 환경(스코프)에 접근할 수 있는 함수
https://www.youtube.com/watch?v=LL0DGc5pg7A (라매개발자 - 자바스크립트 클로저 실용적이고 쉬운 설명)
자바스크립트 클로저에 대한 이해를 하기위해 라매개발자님의 유튜브를 시청 후 정리해보았다.
우선 상황 하나를 가정하여 생각해보자
1. 어떤 변수 cnt가 있다.
2. cnt라는 값은 무조건 cntPlus로만 바꾸고 싶다.
let cnt = 0;
function cntPlus() {
cnt = cnt + 1;
}
console.log(cnt);
cntPlus();
console.log(cnt);
// 출력
// 0
// 1
위와 같이 cntPlus()를 호출 할 경우 cnt가 1 증가됨을 알 수 있다.
하지만 완벽하지가 않다.
만약 이 사이에 1억개의 코드가 있다고 가정하자.
중간에 cnt = 100이라는 코드를 실행시키고, cntPlus를 하게 된다면 101이 출력하게 될 것이다.
어느점이 문제인가?
1. 우리는 cntPlus로만 값을 증가시킬 수 있어야만 한다.
2. 하지만 중간에 cnt = 100 과 같이 cnt라는 변수가 접근이 가능하기 때문에 우리가 생각하는 상황 구현이 힘들다.
그래서 우리는 cnt에 접근을 못하게 해야만 한다.
그 때 필요한게 클로저(Closure)이다.
그 방법은 우선 함수로 한 번 감싸줌으로써 전역변수를 지역변수로 만들어준다.
function closure() {
let cnt = 0;
function cntPlus() {
cnt = cnt + 1;
}
}
console.log(cnt);
cntPlus();
console.log(cnt);
하지만 위와 같이 작성 할 경우 cntPlus또한 클로저 함수의 지역범위에 있는 함수이기 때문에 참조할 수가 없다. 그러기 위해선 cntPlus 함수를 리턴해주게 되는데
function closure() {
let cnt = 0;
function cntPlus() {
cnt = cnt + 1;
}
return {
cntPlus,
};
}
const cntClosure = closure();
console.log(cntClosure);
// cntClosure를 출력해보면
// { cntPlus: [Function: cntPlus] } 와 같이 나오게 되는데
// 이는 cntClosure는 객체고, 이 객체에 cntPlus라는 키값을 가지는 cntPlus 함수가 담겨있는 것을 볼 수 있다.
cntClosure.cntPlus();
해당 상황에서 cntClosure안에 있는 cntPlus함수를 실행시킴으로써 closure 함수 안에 있는 지역 변수인 cnt가 증가하는 것을 알 수 있다.
하지만, 전역에서 console.log를 출력 해 볼 순 없는데 어떻게 해야 출력이 가능할까?
function closure() {
let cnt = 0;
function cntPlus() {
cnt = cnt + 1;
}
function printCnt() {
console.log(cnt);
}
return {
cntPlus,
printCnt,
};
}
const cntClosure = closure();
cntClosure.printCnt();
cntClosure.cntPlus();
cntClosure.printCnt();
// 출력
// 0
// 1
위와 같이 closure 함수 내부에 출력하는 함수를 작성 후 똑같이 리턴해주게 된다면 출력 내용을 볼 수 있다.
위와 같은 방법으로 작성을 하게 된다면 위에 했던 cnt를 바꿔버린 예시와 같이 cnt를 직접적으로 접근하여 바꿀 수 있는 방법은 존재하지 않는다.
또한 추가적인 기능을 작성하고자 하면 closure 함수 내에서 새롭게 함수를 추가 하면 되는데, cnt를 100으로 바꾸는 함수를 작성해보자
function closure() {
let cnt = 0;
function cntPlus() {
cnt = cnt + 1;
}
function setCnt(value) {
cnt = value;
}
function printCnt() {
console.log(cnt);
}
return {
cntPlus,
printCnt,
setCnt,
};
}
const cntClosure = closure();
cntClosure.printCnt();
cntClosure.cntPlus();
cntClosure.printCnt();
cntClosure.setCnt(100);
cntClosure.printCnt();
// 출력
// 0
// 1
// 100
클로저의 용도는 위의 예시와 같은 경우에 사용하는 것이다. (react의 useState의 방법이 클로저와 같은 방법이지 않을까 생각한다)
클로저의 예시
for (var i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i);
}, i * 1000);
}
위의 코드를 작성하게 된다면,
i가 0 ~ 4까지 1초 간격으로 출력이 될 것으로 예상하기가 쉬운데
실제로 출력 된 값을 보면 1초 간격으로 5가 5번 출력된다.
그 이유를 보자면
자바스크립트의 기본 동작은 for문이 먼저 돌고 그 이후에 콜백함수가 실행된다.
그 뜻은 자바스크립트의 엔진에서 for문을 먼저 순회하고, 비동기 콜백함수가 실행되기 때문이다.
함수 안의 변수 i 는 콜백함수가 실행 될 때 값이 결정되고, 반복문이 먼저 돌아 i가 5로 바뀌어 있는 setTimeout 함수가 다섯번 찍혀지게 되는 것이다.
5가 다섯번 찍히는 내부 동작 예시(즉, i가 5로 결정 된 상태에서 setTimeout 함수가 실행된다.)
setTimeout(function(){
console.log(i);
}, 0 * 1000);
setTimeout(function(){
console.log(i);
}, 1 * 1000);
setTimeout(function(){
console.log(i);
}, 2 * 1000);
setTimeout(function(){
console.log(i);
}, 3 * 1000);
// ...
이를 클로저 방식으로 작성하게 된다면 비동기 함수와 반복문의 설계에서 의도한 대로 동작할 수 있다.
for (var i = 0; i < 5; i++) {
function closure(args) {
setTimeout(function () {
console.log(args);
}, i * 1000);
}
closure(i);
}
for문에서 선언한 i를 클로저 함수가 매개변수로 받았고, 이는 콜백함수 이므로 for문의 실행이 끝난 후에도 클로저 함수는 전달받은 i를 초기화 하지 않고 가지고 있게 된다.
내부 동작 예시
function closure(args) {
setTimeout(function() {
console.log(args);
}, 0 * 1000);
}
closure(0);
function closure(args) {
setTimeout(function() {
console.log(args);
}, 1 * 1000);
}
closure(1);
function closure(args) {
setTimeout(function() {
console.log(args);
}, 2 * 1000);
}
closure(2);
...
각각의 콜백 함수들은 실행되는 시점의 i 값에 의해 실행되므로 외부 함수의 실행 컨텍스트는 종료가 되더라도 i 값을 참조 복사 하여 가지고 있기 때문에 각각의 i 값으로 출력이 가능하다.
'Javascript & React' 카테고리의 다른 글
[Javascript] 이벤트 루프 Macro Task Queue & Micro Task Queue (매크로큐, 마이크로 큐) (0) | 2023.03.20 |
---|---|
[React] RTK(Redux Tool Kit)에 대해 알아보자 (0) | 2022.12.28 |
[Javascript] 일급 객체, Callback함수, 고차함수 (0) | 2022.12.23 |
[Javascript] this (일반 함수에서의 this, 화살표 함수에서의 this) (0) | 2022.12.23 |
[Javascript] Javascript에서 함수 호출 때 괄호의 유무? (0) | 2022.12.23 |