-
자바스크립트(JS)의 ClosureCS 2024. 8. 9. 00:41
자바스크립트의 Closure는 this 키워드만큼 중요한 개념 중 하나로, 자바스크립트를 학습하는 과정에서 한 번쯤 들어봤을 내용이다. 클로저는 자바스크립트 고유의 개념이 아닌, 함수를 일급 객체로 취급하는 함수형 프로그래밍 언어에서 사용되는 중요한 특성이다.
웹 개발 문서 저장소인 MDN에서 정의하는 클로저는 다음과 같다.
클로저는 함수와 그 함수가 선언됐을 때의 렉시컬 환경과의 조합이다.
이렇게 말하면 무슨 말인지 한 번에 확 와닿지 않을 거다.
이를 쉽게 말하자면, 클로저는 자신이 생성될 때의 환경을 기억하는 함수라고 할 수 있다.
여기서 정의하는 '함수'란 반환된 내부 함수를 의미하고, '자신이 생성될 때의 환경'이란 내부 함수가 선언됐을 때의 스코프를 의미한다.
이것은 이해를 조금 더 돕기 위해 쉽게 설명한 것이고, 위 MDN에서 정의한 내용을 가지고 살펴보는 것이 좋을 거 같다.
위에서 말한 "함수가 선언될 당시의 렉시컬 환경"이라는 말이 무슨 말인가를 예시와 함께 살펴보도록 하자.
function outer() { var x = 10; var inner = function() { console.log(x); }; inner(); } outer(); // 10
이 예제는 클로저를 설명할 때 가장 기본적인 예제 중 하나이다.
위 코드를 보면 outer 내에 inner 이라는 내부 함수가 선언되고 호출된다.
inner 함수는 자신을 포함하고 있는 outer의 변수 x에 접근할 수 있다.
이는 inner 함수가 outer 함수 내부에 선언되었기 때문이다.
자바스크립트에서 스코프는 함수를 호출할 때가 아니라 함수를 선언한 위치에 따라 결정되는데, 이를 렉시컬 스코핑이라고 한다.
⭐️ 클로저의 동작 원리 ⭐️
function outer() { var a = 2; function inner() { console.log(a); } return inner; // outer 함수에서 inner 함수 반환 } var func = outer(); func(); // 2
이 코드를 보면 outer 함수는 내부 함수 inner 함수를 반환하는 것을 볼 수 있다.
반환된 inner 함수는 outer 함수 내부에서 선언된 변수 a에 접근하고 있으며, func 함수를 호출해도 이 변수에 접근할 수 있다.
여기서 클로저는 inner 함수이다.
inner 함수는 outer 함수의 스코프를 기억하고 있으며, 외부에서 호출되더라도 그 스코프에 접근할 수 있다.
🔥 클로저의 활용 예시 🔥
전역 변수 대신 사용
// 전역 변수를 사용한 카운터 예제 let count = 0; function handleClick() { count++; return count; } // 클로저를 사용한 카운터 예제 function handleClick() { let count = 0; return function () { count++; return count; }; } const btn = document.querySelector('button'); btn.addEventListener('click', handleClick());
위 코드에서 클로저를 활용해 전역 변수를 대체할 수 있다.
클로저를 사용하면 외부 함수인 handleClick의 렉시컬 환경을 참조하는 함수를 통해 전역 변수 없이도 상태를 유지할 수 있다. 또한 클로저는 자바스크립트를 객체지향적으로 사용할 때 데이터 은닉에도 유용하게 사용된다.
반복문에서의 클로저 문제 해결
function func() { for (var i = 1; i < 5; i++) { setTimeout(function () { console.log(i); }, i * 500); } } func(); // 5 5 5 5
위 코드는 반복문을 통해 1 ~ 4까지 정수를 출력하려고 하는 함수였다. 하지만 실제로 이 함수는 5가 4번 출력된다.
이는 setTimeout의 콜백 함수가 실행될 때 i 값이 이미 5로 증가된 상태이기 때문이다.
실행 결과 이를 해결할 수 있는 방법은 다음과 같다.
💡 해결방법 💡
IIFE(즉시 실행 함수 표현식) 사용
function func() { for (var i = 1; i < 5; i++) { (function (j) { setTimeout(function () { console.log(j); }, j * 500); })(i); } } func(); // 1 2 3 4
실행 결과 즉시 실행 함수 표현식인 IIFE를 사용해서 새로운 함수 스코프를 생성하면, 해당 시점의 i 값을 캡처하여 올바른 값을 출력할 수 있다.
블록 스코프를 갖는 let 사용
function func() { for (let i = 1; i < 5; i++) { setTimeout(function () { console.log(i); }, i * 500); } } func(); // 1 2 3 4
실행 결과 let을 사용해 블록 스코프를 생성하면 반복문이 돌 때마다 새로운 i가 선언되고 초기화되므로, 원하는 결과를 얻을 수 있다.
✅ 클로저의 장점과 단점 ✅
장점
- 데이터 은닉 가능: 클로저를 사용해 외부에서 접근할 수 없는 비공개 변수와 메서드를 만들 수 있다.
- 상태 유지: 함수 호출 시 동일한 환경을 유지하면서 특정 데이터를 지속적으로 유지할 수 있다.
단점
- 메모리 사용 증가: 클로저는 상위 함수의 변수를 계속 참조하기에 메모리 누수의 원인이 될 수 있다.
- 디버깅 난이도 증가: 클로저의 특성으로 인해 코드의 흐름을 추적하기 어려워질 수 있고, 복잡한 코드에서는 디버깅이 힘들어질 수 있다.
🧐 클로저와 자바스크립트 모듈 패턴 🧐
클로저는 자바스크립트에서 모듈 패턴을 구현할 때 가장 중요한 역할을 한다.
모듈 패턴은 클로저를 사용해 공개 API와 비공개 API를 구분하는 방식이다.
var Module = (function() { var privateVar = 'I am private'; function privateMethod() { console.log(privateVar); } return { publicMethod: function() { privateMethod(); } }; })(); Module.publicMethod(); // I am private
위 코드에서 Module은 클로저를 통해 privateVar와 privateMethod를 외부에서 숨기고, publicMethod 만을 외부에 노출한다. 이게 바로 클로저의 실용적인 활용 예 중 하나이다.
🔥 React와 Vue.js에서의 클로저 활용 🔥
React에서의 클로저 활용
React에서는 함수형 컴포넌트와 훅('useState', 'useEffect')을 사용할 때 클로저가 등장한다.
특히 useEffect 내에서의 클로저 사용 시, 상태 값이 변할 때마다 클로저가 해당 상태 값을 기억하여 예상치 못한 동작이 발생할 수 있다.
function Counter() { const [count, setCount] = useState(0); useEffect(() => { const timer = setInterval(() => { setCount(prevCount => prevCount + 1); // prevCount가 클로저로 캡처 }, 1000); return () => clearInterval(timer); }, []); // 빈 배열을 전달하면 이펙트는 마운트 시에만 실행 return <div>{count}</div>; }
이 코드에서 setCount의 콜백 함수는 클로저로서 이전 상태인 prevCount를 캡처하여 상태를 업데이트한다.
Vue.js에서의 클로저 활용
Vue.js에서는 methods, computed, watch와 같은 속성에서 클로저를 자주 사용한다.
이러한 속성들은 Vue 컴포넌트의 데이터 상태에 접근할 수 있는 클로저를 통해 컴포넌트의 컨텍스트를 유지한다.
new Vue({ data: { count: 0 }, methods: { increment() { this.count++; // 클로저로 인해 this가 Vue 인스턴스에 바인딩 } } });
🐥 결론 🐥
클로저는 단순히 함수의 개념을 넘어서 자바스크립트의 중요한 프로그래밍 패턴을 구성한다.
우리가 주로 사용하는 웹 프레임워크인 React와 Vue.js에서도 클로저의 개념이 등장하는 만큼, 이를 이해하고 올바르게 활용하는 것이 중요하다.
'CS' 카테고리의 다른 글
자바스크립트(JS)의 클래스 (0) 2024.08.16 자바스크립트(JS)의 prototype (0) 2024.08.09 자바스크립트(JS)의 this (0) 2024.08.08 자바스크립트(JS)의 동작원리 (0) 2024.08.08 throttle과 debounce (0) 2024.08.02