Javascript Closure의 이해

Let's understand closure of javascript
Dec 21, 2023
Javascript Closure의 이해

클로저 개념 및 동작 원리

클로저는 자바스크립트 고유의 개념이 아니라 함수를 일급 객체로 취급하는 함수형 프로그래밍 언어에서 사용되는 중요한 특성이다. MDN에서는 클로저를 아래와 같이 정의한다.
💡
클로저는 함수와 그 함수가 선언됐을 때의 렉시컬 환경과의 조합이다.
여기서 중요한 키워드는 “함수가 선언됐을 때의 렉시컬 환경(Lexical environment)”이다.
예시를 통해서 이해해보자
function outerFunc() { var x = 10; var innerFunc = function () { console.log(x); }; innerFunc(); // 함수를 호출 } outerFunc(); // 10
함수 outerFunc 안에서 내부함수 innerFunc가 선언되고 호출되었다. 이때 내부함수 innerFunc는 자신을 포함하고 있는 외부함수 outerFunc의 변수 x에 접근할 수 있다. 이는 함수 innerFunc가 함수 outerFunc의 내부에 선언되었기 때문이다.
💡
스코프는 함수를 호출할 때가 아니라 함수를 어디에 선언하였는지에 따라 결정된다. 이를 렉시컬 스코핑이라 한다.
  1. innerFunc 함수 스코프 내에서 변수 x를 검색한다. 검색이 실패하였다.
  1. innerFunc 함수를 포함하는 외부 함수 outerFunc의 스코프에서 변수 x를 검색한다. 검색이 성공하였다.
 
이번에는 내부함수 innerFunc를 호출이 아닌 반환하도록 변경해보자.
function outerFunc() { var x = 10; var innerFunc = function () { console.log(x); }; return innerFunc; // 함수를 반환 } /** * 함수 outerFunc를 호출하면 내부 함수 innerFunc가 반환된다. * 그리고 함수 outerFunc의 실행 컨텍스트는 소멸한다. */ var inner = outerFunc(); // outerFunc 실행과 동시에 innerFunc 반환 inner(); // 10
함수 outerFunc는 실행된 이후 콜스택(실행 컨텍스트 스택)에서 제거되었으므로 함수 outerFunc의 변수 x 또한 더이상 유효하지 않게 되어 변수 x에 접근할 수 있는 방법은 달리 없어 보인다.
그러나 위 코드의 실행 결과는 변수 x의 값인 10이다. 자신을 포함하고 있는 외부함수보다 내부함수가 더 오래 유지되는 경우, 외부 함수 밖에서 내부함수가 호출되더라도 외부함수의 지역 변수에 접근할 수 있다. 이러한 함수를 클로저(Closure)라고 부른다.
 
위에서 정의했던 내용을 재정의하면 클로저는 반환된 내부함수가 자신이 선언됐을 때의 환경의 스코프를 기억하여 자신이 선언됐을 때의 환경 밖에서 호출되어도 그 환경에 접근할 수 있는 함수라고 할 수 있다.
💡
내부함수 실행 컨텍스트 내의 활성 객체(변수, 함수 선언 등의 정보를 가지고 있는 객체)에서 외부함수를 참조하는 한 외부함수는 유효하여 내부함수가 참조할 수 있다.

클로저 사용 예시

클로저가 가장 유용하게 사용되는 상황은 현재 상태를 기억하고 변경된 최신 상태를 유지하는 것이다. 아래 예제를 살펴보자.
<!DOCTYPE html> <html> <body> <button class="toggle">toggle</button> <div class="box" style="width: 100px; height: 100px; background: red;"></div> <script> const toggleBtn = document.querySelector('.toggle'); const box = document.querySelector('.box'); const toggle = (function () { let isShow = false; // 1. 클로저를 반환 return function () { box.style.display = isShow ? 'block' : 'none'; // 3. 상태 변경 isShow = !isShow; }; })(); // 2. 이벤트 프로퍼티에 클로저를 할당 toggleBtn.onclick = toggle; </script> </body> </html>
  1. 즉시실행함수는 함수를 반환하고 즉시 소멸한다. 즉시실행함수가 반환한 함수는 자신이 생성됐을 때의 렉시컬 환경(Lexical environment)에 속한 변수 isShow를 기억하는 클로저다.
  1. 클로저를 버튼이 클릭될 때마다 실행할 함수로서 할당했다. 클로저를 제거하지 않는 한 클로저가 기억하는 렉시컬 환경의 변수 isShow는 소멸하지 않는다.
  1. 버튼을 클릭하면 이벤트 프로퍼티에 할당한 이벤트 핸들러인 클로저가 호출된다. 이때 .box 요소의 표시 상태를 나타내는 변수 isShow의 값이 변경된다. 변수 isShow는 클로저에 의해 참조되고 있기 때문에 유효하다.

클로저의 장단점

  • 장점
    • 정보 은닉: 클로저를 사용하면 외부에서 접근할 수 없는 private 변수를 생성하고, 해당 변수에 접근할 수 있는 getter와 setter 함수를 반환하는 형태로 구현할 수 있다. 이를 통해 데이터를 보호하고 캡슐화할 수 있다.
      • function createPerson(name) { let age = 0; return { getName: function() { return name; }, getAge: function() { return age; }, setAge: function(newAge) { age = newAge; } }; } const person = createPerson('John'); console.log(person.getName());// "John" 출력 console.log(person.getAge());// 0 출력 person.setAge(30); console.log(person.getAge());// 30 출력
    • 외부 변수 접근: 클로저를 사용하면 내부 함수가 외부 함수의 변수에 접근할 수 있다. 이를 활용하여 비동기 처리, 콜백 함수 등에서 외부 변수를 사용할 수 있다.
      • function greet(name) { setTimeout(function() { console.log(`Hello, ${name}!`); }, 1000); } greet('John');// 1초 후에 "Hello, John!" 출력
  • 단점
    • 메모리 누수: 클로저는 외부 함수의 변수를 참조하므로, 클로저가 참조하는 변수가 메모리에 유지된다. 이는 사용하지 않는 변수에 대한 참조를 제거하지 않을 경우 메모리 누수가 발생할 수 있다. 따라서, 클로저를 사용할 때는 필요한 변수만 유지하고 사용하지 않는 변수는 적절히 해제해야 한다.
      • function outerFunction() { let outerVariable = 'Outer'; function innerFunction() { console.log(outerVariable); } outerVariable = null;// 클로저에 의해 참조되는 변수에 대한 할당 해제 return innerFunction; } const closure = outerFunction(); closure();// null 출력
    • 성능 이슈: 클로저를 사용할 때는 메모리 사용량이 증가하고 함수 호출 시 추가적인 실행 비용이 발생할 수 있습니다. 따라서, 클로저의 남용은 성능 저하를 초래할 수 있습니다.
    •  
Share article

samuel-dev-world