ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Notes from reading 🔖 You Don't Know JS Yet - 12
    JavaScript 2026. 5. 17. 17:28

    ### 파트2 - Chapter 7 클로저 사용법

     

    • 클로저또한 최소 노출의 원칙 (POLE)을 기반으로 한다.
    • 클로저를 사용하면, 변수를 외부 스코프에 두는 대신에 더 제한된 스코프로 캡슐화가 가능하다.
      • 이를 통해서 함수 내부에서 함수 밖 해당 변수에 계속 접근이 가능하여 변수를 더 넓은 범위에서 사용 가능
      • 클로저를 통해 참조된 스코프 변수를 기억하기 때문
    • 클로저는 함수 안에서만 일어나는 함수의 동작. 함수를 다루지 않으면, 클로저는 적용 X
    • 객체, 클래스는 클로저를 가질 수 없다. 함수만 가능

     

    • 화살표 함수도 클로저가 있다.
    • 작은 화살표 함수를 사용하더라도, 클로저가 생성될 수 있다.
    • 클로저는 단순 함수의 코드에 의해 정의되는 단일 렉시컬 정의가 아니라, 함수 인스턴스에 따라 다르게 생성된다.
      • 인스턴스가 생성될 때 마다 각각 인스턴스는 다른 독립적인 클로저를 가진다는 의미인듯
    • 실제 클로저의 동작은 실행 시점에 함수 인스턴스에 따라 달라진다.

     

     

    • 클로저는 스냅샷이 아닌 라이브 링크이다. (생성되는 시점에 고정되는 것이 아니라는 뜻)
    • 라이브 링크이기 때문에, 수정/재할당이 가능하다 -> 클로저 함수가 많이 쓰이는 이유이기도 함.
    • 클로저의 외부 스코프는 꼭 함수 스코프이지 않아도 된다. (일반적으로 함수 스코프이긴 함)
    • 내부 함수를감싸는 외부 스코프가 존재하기만 해도 클로저가 됨
    • 클로저는 특정 시점의 값을 저장하는 그런 기능이 아님!

     

    • 콜백을 사용할 때 클로저는 자주 사용된다. (Ajax, 이벤트 등)
    • 클로저는 비동기적인 환경에서도 변수에 접근할 수 있게 해준다.
    • 클로저 -> 관찰 가능성 O. 하지만, 작성한 프로그램에서 관찰이 X 하면 ? 괜찮지 않다.
      • 전역 변수에 접근할 때가 그 예시
      • 변수가 존재하기만 하고, 한 번도 해당 변수에 접근하지 않는 경우에 클로저 관측 X
      • 함수가 호출되지 않을때도 그렇다.

     

    클로저의 정의 ✨

    • 반드시 함수와 연관됨
    • 외부 스코프 변수를 하나 이상 참조해야 함
    • 참조하려는 변수가 있는 스코프 체인의 다른 분기에서 함수를 호출해야 함

    => 관찰 가능성

     

     

    /**
     * ============================================
     * Closure (클로저) 완전 정리 예제
     * ============================================
     *
     * 이 파일 하나로 아래 내용을 모두 설명:
     *
     * 1. 기본 클로저
     * 2. 클로저는 스냅샷이 아닌 라이브 링크
     * 3. 함수 인스턴스마다 다른 클로저
     * 4. 화살표 함수 클로저
     * 5. 객체 자체는 클로저가 아님
     * 6. 비동기 + 콜백에서의 클로저
     * 7. 관찰 가능성(observability)
     * 8. 전역 변수와 클로저 차이
     */
    
    /* =========================================================
     * 1. 가장 기본적인 클로저
     * =========================================================
     */
    
    console.log("===== 1. 기본 클로저 =====");
    
    function outer() {
      // outer 함수의 지역 변수
      let count = 0;
    
      // inner 함수는 outer 스코프의 count를 참조
      function increase() {
        count++;
        console.log("count:", count);
      }
    
      // increase 함수 반환
      return increase;
    }
    
    // outer 실행 종료
    // 하지만 increase는 count를 계속 기억함
    const fn = outer();
    
    fn(); // 1
    fn(); // 2
    fn(); // 3
    
    /**
     * 핵심:
     *
     * 원래라면 outer 종료 후 count는 사라져야 함.
     * 하지만 increase 함수가 count를 계속 참조하고 있으므로
     * JS 엔진이 메모리에서 유지함.
     *
     * 이것이 클로저.
     */
    
    /* =========================================================
     * 2. 클로저는 스냅샷이 아니라 라이브 링크
     * =========================================================
     */
    
    console.log("\n===== 2. 라이브 링크 =====");
    
    function liveLinkExample() {
      let value = 1;
    
      function print() {
        console.log("value:", value);
      }
    
      function change() {
        value = 999;
      }
    
      return {
        print,
        change,
      };
    }
    
    const live = liveLinkExample();
    
    live.print(); // 1
    
    // 외부 변수 수정
    live.change();
    
    live.print(); // 999
    
    /**
     * 만약 클로저가 "값 복사"였다면:
     *
     * 1
     * 1
     *
     * 이 출력되어야 함.
     *
     * 하지만 실제로는:
     *
     * 1
     * 999
     *
     * 이 출력됨.
     *
     * 즉 클로저는 값을 복사하는 게 아니라
     * "살아있는 변수 참조(live reference)" 를 유지함.
     */
    
    /* =========================================================
     * 3. 함수 인스턴스마다 서로 다른 클로저
     * =========================================================
     */
    
    console.log("\n===== 3. 함수 인스턴스마다 다른 클로저 =====");
    
    function makeCounter() {
      let count = 0;
    
      return function () {
        count++;
        console.log(count);
      };
    }
    
    // 각각 새로운 함수 인스턴스 생성
    const counter1 = makeCounter();
    const counter2 = makeCounter();
    
    /**
     * counter1 과 counter2 는 같은 코드로 만들어졌지만
     * 서로 다른 count를 가진다.
     *
     * 즉 makeCounter() 호출 때마다
     * 새로운 클로저 생성.
     */
    
    counter1(); // 1
    counter1(); // 2
    
    counter2(); // 1
    counter2(); // 2
    
    /* =========================================================
     * 4. 화살표 함수도 클로저 가능
     * =========================================================
     */
    
    console.log("\n===== 4. 화살표 함수 클로저 =====");
    
    function arrowClosure() {
      let name = "citron";
    
      // 화살표 함수도 외부 스코프 기억 가능
      return () => {
        console.log(name);
      };
    }
    
    const arrowFn = arrowClosure();
    
    arrowFn();
    
    /**
     * 클로저는 함수 종류와 관계없음.
     *
     * 가능:
     * - 일반 함수
     * - 함수 표현식
     * - 화살표 함수
     */
    
    /* =========================================================
     * 5. 객체 자체는 클로저가 아님
     * =========================================================
     */
    
    console.log("\n===== 5. 객체와 클로저 =====");
    
    // 단순 객체
    const normalObject = {
      value: 10,
    };
    
    console.log(normalObject.value);
    
    /**
     * 이건 클로저가 아님.
     *
     * 이유:
     * - 함수 없음
     * - 렉시컬 스코프 캡처 없음
     */
    
    // 실제 클로저 예제
    function objectWithClosure() {
      let secret = "hidden";
    
      return {
        getSecret() {
          return secret;
        },
      };
    }
    
    const obj = objectWithClosure();
    
    console.log(obj.getSecret());
    
    /**
     * 여기서 클로저를 만드는 건 객체가 아니라
     * getSecret 함수.
     */
    
    /* =========================================================
     * 6. 비동기 + 콜백에서의 클로저
     * =========================================================
     */
    
    console.log("\n===== 6. 비동기 콜백 =====");
    
    function fetchData() {
      let data = "important data";
    
      // setTimeout 콜백은 나중에 실행됨
      setTimeout(() => {
        // 하지만 data 접근 가능
        console.log(data);
      }, 1000);
    }
    
    fetchData();
    
    /**
     * fetchData 함수는 이미 끝났는데
     * 1초 뒤에도 data 접근 가능.
     *
     * 이유:
     * 콜백 함수가 data를 클로저로 기억하기 때문.
     *
     * 실무에서 가장 흔한 클로저 사용 사례:
     * - 이벤트 핸들러
     * - Ajax
     * - Promise
     * - setTimeout
     * - React Hook
     */
    
    /* =========================================================
     * 7. 관찰 가능성(observability)
     * =========================================================
     */
    
    console.log("\n===== 7. 관찰 가능성 =====");
    
    function observableClosure() {
      let x = 10;
    
      return function () {
        console.log(x);
      };
    }
    
    const observable = observableClosure();
    
    observable();
    
    /**
     * outer 함수 종료 후에도 x 접근 가능.
     * 우리가 실제로 확인 가능.
     *
     * => 관찰 가능한 클로저
     */
    
    // 관찰 불가능한 예시
    function notObservable() {
      let y = 20;
    
      function inner() {
        console.log(y);
      }
    
      // inner 호출 안 함
      // 반환도 안 함
    }
    
    /**
     * 여기서는 클로저 존재 여부를
     * 외부에서 관찰할 수 없음.
     *
     * 실질적인 의미가 거의 없음.
     */
    
    /* =========================================================
     * 8. 전역 변수와 클로저 차이
     * =========================================================
     */
    
    console.log("\n===== 8. 전역 변수 =====");
    
    let globalValue = 10;
    
    function printGlobal() {
      console.log(globalValue);
    }
    
    printGlobal();
    
    /**
     * 기술적으로 스코프 체인을 사용하긴 함.
     *
     * 하지만 보통 이런 경우를
     * 대표적인 클로저라고 부르진 않음.
     *
     * 클로저의 핵심은:
     *
     * "이미 종료된 외부 함수의 변수를
     * 계속 기억하는 것"
     */
    
    /* =========================================================
     * 9. 최종 정리
     * =========================================================
     */
    
    /**
     * 클로저의 핵심 조건:
     *
     * 1. 함수가 존재해야 함
     * 2. 외부 스코프 변수를 참조해야 함
     * 3. 함수가 다른 스코프에서 실행되어야 함
     * 4. 외부 함수 종료 후에도 변수 접근 가능해야 함
     *
     * 한 줄 요약:
     *
     * "함수가 자신이 생성된 당시의 스코프를 기억하는 것"
     */

     

Designed by Tistory.