Skip to content

Latest commit

 

History

History
167 lines (128 loc) · 7.19 KB

File metadata and controls

167 lines (128 loc) · 7.19 KB

❓클로저란 "클로저는 함수와 그 함수가 선언된 렉시컬 환경과의 조합이다." => 무슨 말인지는 찬찬히 알아보자

24.1 렉시컬 스코프

  • 자바스크립트 엔진은 함수를 어디서 호출했는지가 아니라 함수를 어디에 정의했는지에 따라 상위 스코프를 결정한다.➡️ 렉시컬 스코프(정적 스코프)

    const x = 1;
    
    function foo() {
      const x = 10;
      bar();
    }
    
    function bar() {
      console.log(x);
    }
    
    foo();
    bar();

➡️

  • foo()와 bar() 모두 전역에서 정의된 전역 함수로 상위 스코프는 전역이다.
  • 함수의 상위 스코프는 함수를 정의한 위치에 의해 정적으로 결정되고 변하지 않는다.
  • "함수의 상위 스코프를 결정하는 것" == "렉시컬 환경의 외부 렉시컬 환경에 대한 참조에 저장할 참조값을 결정하는 것"
  • 렉시컬 환경의 "외부 렉시컬 환경에 대한 참조"에 저장할 참조값, 즉 상위 스코프에 대한 참조는 함수 정의가 평가되는 시점에 함수가 정의된 환경(위치)에 의해 결정된다.

24.2 함수 객체의 내부 슬롯 [[Environment]]

  • 함수가 정의된 환경(위치)와 호출되는 환경(위치)는 다를 수 있다. 렉시컬 스코프가 가능하려면 함수는 자신이 호출되는 환경과는 상관없이 자신이 정의된 환경(상위 스코프)를 기억해야한다. → 함수는 자신의 내부 슬롯에 자신이 정의된 환경(상위 스코프의 참조)를 저장한다. → 상위 스코프의 참조는 현재 실행 중인 실행 컨텍스트의 렉시컬 환경을 가리킨다.
  • 함수 객체의 내부 슬롯에 저장된 현재 실행중인 실행 컨텍스트의 렉시컬 환경의 참조 == 상위 스코프 자신이 호출되었을 때 생성될 함수 렉시컬 환경의 "외부 렉시컬 환경에 대한 참조"에 저장될 참조값 함수 객체는 내부 슬롯 [[Environment]]에 저장한 렉시컬 환경의 참조(상위 스코프)를 자신이 존재할 때까지는 기억한다.
const x = 1;

function foo() {
  const x = 10;

  // 상위 스코프는 "함수 정의 환경"에 따라 결정된다.
  // 함수 호출 위치와 상위 스코프는 아무 관계가 없다.
  bar();
}

// 함수 bar는 자신의 상위 스코프, 즉 전역 렉시컬 환경을 [[Environment]]에 저장하여 기억한다.
function bar() {
  console.log(x);
}

foo();
bar();

➡️

  • foo()와 bar() 함수는 모두 전역 코드가 평가되는 시점에 함수 객체를 생성하고 전역 객체 window의 메서드가 된다. → 전역 코드 평가 시점에 실행 중인 실행 컨텍스트의 렉시컬 환경인 전역 렉시컬 환경의 참조가 저장된다.
  • 함수 호출 시 함수 내부로 코드의 제어권이 이동한다.
  • 함수 코드를 평가한다. [함수 코드 평가 순서]
    1. 함수 실행 컨텍스트 생성
    2. 함수 렉시컬 환경 생성 2.1. 함수 환경 레코드 생성 2.2. this 바인딩 2.3. 외부 렉시컬 환경에 대한 참조 결정
  • 외부 렉시컬 환경에 대한 참조는 함수 객체의 내부 슬롯에 저장된 렉시컬 환경의 참조가 할당된다.

24.3 클로저와 렉시컬 환경

const x = 1;

// ①
function outer() {
  const x = 10;
  const inner = function () {
    console.log(x);
  }; // ②
  return inner;
}

// outer 함수 호출 시, 중첩 함수 inner는 반환한다.
// outer 함수의 실행 컨텍스트는 실팽 컨텍스트 스택에서 팝되어 제거된다.
const innerFunc = outer(); // ③
innerFunc(); // ④ 10
  • outer() 호출 시 중첩 함수 inner()를 반환하고 생명주기를 마감한다. outer() 실행 종료 시 outer 함수의 실행 컨텍스트는 스택에서 제거된다. 그러나, ④ 실행 결과는 outer 함수의 지역 변수 x의 값인 10이다.

  • 외부 함수보다 중첩 함수가 더 오래 유지되는 경우 중첩 함수는 이미 생명 주기가 종료된 외부 함수의 변수를 참조 가능하다. → 클로저라고 지칭한다

    즉, 정의에서 본 "그 함수가 선언된 렉시컬 환경"은 상위 스코프를 의미하는 실행 컨텍스트의 렉시컬 환경을 뜻한다.

  • inner 함수는 선언될 당시 자신을 감싼 outer의 렉시컬 환경을 기억함. 즉, inner 함수는 자신이 어디서 호출될지와 상관없이, outer 함수의 스코프를 기억하는 클로저가 되는 것.

    • outer()는 inner를 반환함.
    • 이때 outer 함수의 실행이 끝나면 x = 10을 포함한 실행 컨텍스트는 일반적으로는 사라져야 하지만… 💡 inner 함수가 반환되면서 외부 변수에 의해 참조되고 있으므로, outer의 변수 환경은 **GC(가비지 컬렉터)**에 의해 수거되지 않고 heap 메모리에 유지됨. 이게 바로 클로저의 핵심!
      📦 Call Stack
    --------------------------
    | innerFunc() 호출        |
    | outer() 호출 (끝남)     |
    --------------------------
    
    🧠 Heap Memory
    --------------------------
    | innerFunc (function)     | ---> 📎 [[Environment]]: outer's scope
    | outer() 환경 참조 보존   |     └─ x: 10
    --------------------------
    
  • 클로저에 의해 참조되는 상위 스코프의 변수를 자유 변수라고 지칭한다.

24.4 클로저의 활용

클로저는 상태를 안전하게 변경하고 유지하기 위해 사용한다. 즉, 상태를 안전하게 은닉하고 특정 함수에게만 상태 변경을 허용하기 위해 사용한다.

💡 상태 유지

const createCounter() {
  	// 카운트 상태 변수
    let count = 0;  // 외부 함수의 변수 (상태)

  	// 클로저인 메서드를 갖는 객체를 반환하다.
  	// 객체 리터럴은 스코프를 만들지 않는다.
  	// 아래 메서드들의 상위 스코프는 즉시 실행 함수의 렉시컬 환경이다.
    return function() {
        count++;  // 클로저로 외부 함수의 변수인 count에 접근
        console.log(`현재 카운트: ${count}`);
    };
}

const counter = createCounter();
counter();  // "현재 카운트: 1"
counter();  // "현재 카운트: 2"
counter();  // "현재 카운트: 3"

24.5 캡슐화와 정보 은닉

❓ 캡슐화란

  • 객체의 생태를 나타내는 프로퍼티와 프로퍼티를 참조하고 조작할 수 있는 동작인 메서드를 하나로 묶는 객체의 특정 프로퍼티나 메서드를 감출 목적으로 사용
  • 객체 간의 상호 의존성(결합도)를 낮추는 효과가 있음
  • 객체지향 프로그래밍 언어는 클래스 정의 클래스를 구성하는 멤버(프로퍼티와 메서드)에 대하여 public, private, protected 같은 접근 제한자를 선언하여 공개 범위를 한정함
  • public으로 선언된 프로퍼티와 메서드는 클래스 외부에서 참조할 수 있지만, private으로 선언된 경우는 클래스 외부에서 참조 불가능

→ JS는 접근 제한자를 제공하지 않고 객체의 모든 프로퍼티와 메서드는 외부에 공개됨. 즉, 기본적으로 public이다.