ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 자바스크립트(JS)의 Closure
    CS 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
Designed by Tistory.