클로저는 함수와 그 외부상태를 결합한 것으로, 데이터 은닉과 콜백을 생성하는 유용한 개념입니다. JS 언어를 사용하는 개발자라면 한번쯤은 알게모르게 사용한 경험이 있을 것 같습니다. 자바스크립트에서 정말 중요한 개념이라고 생각해서, 이의 장점과 단점을 이해하고 코드를 작성한다면 코드의 질이 달라지지 않을까 생각이 듭니다.
클로저(Closure) 기본 개념 🐶
클로저(Closure)란 외부 변수를 접근하는 가장 강력한 개념입니다. 즉 함수와 함수가 선언되었을 때의 렉시컬 환경의 조합입니다. 자신의 중괄호 외부의 값을 접근할 수 있으며, 이 과정에서 메모리의 힙을 활용하여 참조 가능한 변수를 저장함으로써 불필요한 데이터 노출을 방지합니다. 또 이를 이용해 자바스크립트(javascript) API 사용 시 더욱 효율적인 콜백 구조를 만들 수 있습니다.
🕹️ 렉시컬 환경이란?
코드가 어디서 실행되며 주변에 어떤 코드가 있는지를 뜻하며, 이를 구현한 것이 실행 컨텍스트입니다.
🕹️ 렉시컬 스코프란?
함수를 어디에 선언했는지에 따라 상위 스코프가 결정되는 것. 정적 스코프라고도 불립니다.
클로저란 내부 함수가 외부 함수의 스코프에 접근할 수 있게 해주며, 외부 함수의 실행이 끝나고 난 후에도 외부 함수의 변수에 접근이 가능하게 합니다. 클로저를 통해 생성된 함수는 각자의 독립적인 변수 스코프를 가지게 되고 이는 데이터의 안전성과 무결성을 보장합니다. 또 메모리 관리와 데이터 접근 제어에 유용하게 사용할 수 있습니다.
클로저(Closure)의 동작방식 🏓
자바스크립트(javascript)에서의 함수는 실행될때마다 각자의 실행 컨텍스트를 생성합니다. 이 실행 컨텍스트는 함수의 콜스택에 쌓이게 되며, 함수 실행이 완료되면 컨텍스트는 스택에서 제거됩니다. 여기서 함수의 지역 변수와 매개변수는 스택 프레임에 저장됩니다. 문자열과 참조타입은 각각 스택과 힙에 저장됩니다. 함수가 종료되면 지역변수와 매개변수는 메모리에서 사라지고, 불필요한 것들은 제거됩니다.
내부 함수가 외부함수의 변수에 접근하는 경우, 외부 함수의 실행 컨텍스트는 내부 함수에 의해 참조되므로 가비지 컬렉션의 대상이 되지 않습니다. 이로 인해 외부 함수의 변수는 내부 함수가 존재하는 한 계속해서 접근 가능한 상태로 남게됩니다.
👉 클로저는 함수의 렉시컬 환경을 함께 참조하기 때문에, 함수가 실행 컨텍스트에서 제거된 후에도 해당 환경에 있는 변수에 접근할 수 있습니다.
🕹️ 가비지 컬렉션(Garbage Collection)이란?
줄여서 GC라고 부릅니다. 가비지 컬렉션은 메모리 관리 방법 중에 하나로, 시스템에서 더 이상 사용하지 않는 동적 할당된 메모리 블럭을 찾아서 자동으로 다시 사용 가능한 자원으로 회수하는 것
클로저 예시
for(var i=0; i<3; i++){
const log = () => {
console.log(i);
}
setTimeout(log, 100);
}
이 코드에서 변수 i를 var 키워드로 선언하고, for루프를 이용해서 이 변수를 세번 증가시킵니다. for루프안에선 클로저가 작동하며, 로그를 출력하는 log 함수를 정의합니다. 타임아웃을 설정하고 log 함수를 콜백으로 전달하면 100ms 후에 log 함수가 실행되는 작업이 대기열에 등록이 됩니다. 여기서 출력이 ' 0,1,2 '가 되어야 할 것 같지만 실제로 '3'이 세번 출력이 됩니다.
이를 이해하려면 var과 let의 차이를 알아야 됩니다. for 루프에서 var을 사용하면 해당 변수가 부모 범위로 끌어올려지며, let을 사용하면 for 루프 범위로 국한된 변수가 생성됩니다.
for(let i=0; i<3; i++){
const log = () => {
console.log(i);
}
setTimeout(log, 100);
}
이제 반대로 let을 사용할 경우, 클로저는 i 변수를 함께 캡처합니다. 이 루프의 각 반복은 ' 0,1,2 ' 가 되며, 클로저가 없다면 자바스크립트는 i 변수를 호출 스택의 메모리에 할당하고 즉시 해제합니다. 하지만 여기서 클로저가 존재하다면 해당 변수가 힙 메모리에 저장되어 미래에 타이머가 호출될때 다시 참조 가능합니다.
var을 사용할 경우, 전역 변수에 대한 참조를 캡처하므로 타이머가 100ms 후에 실행되며, 루프가 3까지 반복된 후에 세번 로그에 출력하게 됩니다. let을 사용하면 블록스코프 변수가 생성되지만, var을 사용하면 해당 변수는 전역 스코프에 있게되어 클로저에 의해 포착되는 방식이 달라집니다.
클로저를 이용한 리액트 훅 useState
function useState(initialValue) {
let _value = initialValue;
const state = () => _value;
const setState = (newValue) => {
_value = newValue;
};
return [state, setState];
}
const [count, setCount] = React.useState(1);
console.log(count()); // 1
setCount(2);
console.log(count()); // 2
리액트 훅의 useState 또한 클로저의 상태 유지 특성을 활용한 개념입니다. [state, setState]가 선언되는 시점에서 useState의 호출이 끝나게 되지만, 클로저가 내부의 _value값을 기억하고 있기 때문에 이후에도 접근이 가능하게 됩니다.
그 외
클로저를 사용하면 이에 의해서 내부 함수는 외부 함수의 변수를 참조하고 있습니다. 그러므로 외부 함수가 종료되어도 가비지 콜렉터에 의해 메모리가 해결되지 않습니다. 즉 클로저를 사용하면 메모리 측면에서 안좋을 수 있습니다. 이에 대한 해결방법으로 클로저를 할당한 변술에 null을 할당함으로써 메모리를 해제시킵니다.
항상 null을 할당하면서 메모리를 해제시키는 것은 그다지 좋은 방법은 아니라고 생각하고, 치명적인 단점 또한 아니라서 굳이 쓰지 않을 것 같습니다.
결론
클로저는 자바스크립트를 사용하면 알아야 하는 중요한 개념이라고 생각합니다. 클로저를 통해서 데이터 정보를 은닉하고 캡슐화할 수 있으며 다양한 곳에서 이 장점들을 활용해야 합니다.
reference
JavaScript의 클로저(Closure)란? (feat. React의 useState)
JavaScript의 클로저의 원리를 쉽게 배워보고 어떤 상황에 사용되는지 알아보도록 하겠습니다.
enjoydev.life
'프론트엔드 기록 > 자바스크립트' 카테고리의 다른 글
Next.js | web3.js, MetaMask(메타마스크) 지갑 연결 및 서명 (1) | 2024.09.20 |
---|