[DAY11] 컨텍스트.. 스코프.. 클로저

Jhey·2024년 10월 28일
0

JavsScript

목록 보기
6/18

0. 컨텍스트와 스코프

0.1 기본 실행 과정

  1. 맨 처음 실행 컨텍스트가 콜스택에 쌓이고 생성 단계에 들어선다.

  2. 환경 레코드는 내부적으로 생성 단계와 실행 단계라는 두 가지 단계를 거쳐서 실행한다.

    생성 단계에서 일어나는 일:

    • 변수 선언 및 초기화
    • this 키워드 바인딩
    • 스코프에 범위 결정

    실행 단계에서 일어나는 일:

    • 코드 실행 또는 업데이트
  3. outer는 바깥 렉시컬 스코프 (record + outer)를 참조하는 객체입니다. 아우터를 통해 외부에 있는 식별자를 참조할 수 있습니다.

  4. 역할이 끝나면 콜스택에서 제거된다.

0.2 지역 스코프와 지역 스코프 실행 과정

function a() {
	let a = 0;
}
function b() {
  	let b = 0;
	console.log(a);
}
a();
b();

결과:
[Function: a]
  1. 맨 처음 생성 과정에서 함수 a, b가 기록된다.
  2. a 함수 실행 컨텍스트가 쌓인다.
  3. a 함수 실행 후 a 함수 실행 컨텍스트를 제거한다.
  4. b 함수 실행 컨텍스트가 쌓인다.
  5. b 함수가 outer를 통해 상위 스코프를 참조하려 하지만 a가 이미 사라져서 참조할 수 없다.
  6. 그래서 a 출력 시 생성 과정에서 record에 기록한 a가 함수라는 사실을 출력한다.

0.3 지역 스코프와 지역 스코프 실행 과정

if (true) {
    let x = 10;
    console.log(x); // 10
}
console.log(x); // Error: x is not defined
  • 같은 스코프를 공유하지만 블록 스코프 내부는 공유할 수 없다.
  • 스코프 안에 방 하나가 존재하는 느낌이다.


1. 클로저

1.1 사전 지식

1.1.1 가비지 컬렉터

  • 메모리에서 더 이상 사용되지 않는 객체를 자동으로 감지하고, 해당 메모리를 회수하여 메모리에서 삭제해주는 시스템입니다.

1.1.2 가비지 컬렉션

  1. 메모리가 회수되는 과정
  2. 가비지 컬렉션을 자동으로 수행합니다.

1.1.3 콜스택과 가비지 컬렉터

  • 콜스택에서 실행 컨텍스트가 제거되는 순간 자동으로 가비지 컬렉터의 대상이 되어 제거됩니다.
  • 하지만 자동으로 수행되지 않는 경우가 있습니다. → 컨텍스트의 레코드 값이 참조 중일 때


1.2 그것이 클로저

  • 콜스택에서 컨텍스트가 제거되어 가비지 컬렉터가 실행되어야 하는데, 아직 참조(가 유지)하고 있는 record가 있어서 자동으로 수행되지 않는 현상입니다.

1.2.1 다양한 정의를 가진 클로저

  • 내부 함수에서 외부 변수를 참조하면서 반환되는 경우
  • 자유 변수를 참조하는 내부 함수를 반환하는 것
    *자유 변수: 클로저가 되게 하는 변수, 가비지 대상이 되지 못하게 하는 변수입니다.
  • 가비지 컬렉션의 대상이 되지 못하고 메모리에 남아 있는 현상입니다.

1.2.2 클로저의 위험성

  • 메모리가 자동으로 삭제되지 않음
  • 클로저가 많아지면 회수되지 않은 메모리가 많아져 자바스크립트의 가비지 컬렉터 시스템이 위협받습니다.
  • 그래서 마지막에 함수 = null을 넣어서 클로저를 풀어주어야 합니다.


1.3 클로저 사용 패턴

1.3.1 캡슐화

실제 구현 내용 일부를 외부에 감추어 은닉합니다.

function createCounter() {
  let cnt = 1; // 외부 함수에만 존재하는 cnt
  return function () { // 내부 함수에서 cnt를 사용하는 클로저
    return cnt++;
  };
}

const counter = createCounter();
console.log(counter()); // 1

// 외부에서 cnt에 접근하려고 하면:
console.log(cnt); // ReferenceError: cnt is not defined
  1. createCounter 함수가 실행되어 cnt를 생성하고, 내부 함수를 반환합니다.
  2. cnt는 createCounter의 스코프에서만 유효한 변수지만, 내부 함수에서 이를 참조하고 있기 때문에 메모리에서 유지됩니다.
  3. 외부에서는 cnt에 접근할 방법이 없고, 내부 함수는 클로저로 cnt를 기억하고 있으므로, 반환된 함수만이 cnt를 안전하게 조작할 수 있습니다.

1.3.2 함수 팩토리

파라미터에 따라 서로 다른 객체를 생성할 수 있습니다. 함수를 동적으로 생성할 수 있습니다.

function delayExecution(ms) {
  return function (callback) {
    setTimeout(callback, ms);
  };
}

const delayedFunc = delayExecution(1000);
// 매개변수를 함수로 줌
delayedFunc(() => console.log("Executed after 1 second"));
  1. delayExecution 함수가 실행되고 ms 매개변수로 1000이 전달됩니다. delayExecution 함수는 내부 함수를 반환하며, 이 내부 함수는 callback이라는 매개변수를 받습니다.
  2. 반환된 내부 함수(delayedFunc)에 콜백 함수로 () => console.log("Executed after 1 second")가 전달됩니다.
  3. 내부 함수에서는 setTimeout을 호출하며, callback 함수와 ms 값을 사용해 1초 후에 콜백 함수가 실행되도록 합니다.
  4. 여기서 클로저가 발생하는데, 이는 내부 함수가 외부 함수의 ms 변수에 접근할 수 있게 해주는 JavaScript의 특성 때문입니다. delayExecution 함수가 이미 실행되고 종료되었지만, 내부 함수는 ms에 접근하고 있어 delayExecution의 실행 컨텍스트가 메모리에서 사라지지 않고 유지됩니다.

이로 인해 delayedFunc 내부에서는 언제든 ms 값을 참조할 수 있고, delayExecution의 환경이 사라지지 않고 남아 있는 것이 클로저입니다.


1.3.3 메모이제이션

함수의 결과를 캐싱하여 동일한 입력에 대해 반복 계산을 방지하는 방법으로, 클로저를 활용하여 이전 결과를 기억하도록 합니다.

function memoizeSquare() {
  const cache = {}; // 이전 계산 결과를 저장하는 객체

  return function (n) {
    if (n in cache) {
      console.log("Fetching from cache:", n);
      return cache[n]; // 캐시에 결과가 있으면 반환
    }
    console.log("Calculating result for:", n);
    const result = n * n; 
    cache[n] = result; // 새 계산 결과를 캐시에 저장
    return result;
  };
}

const square = memoizeSquare();

console.log(square(5)); // "Calculating result for: 5", 25
console.log(square(5)); // "Fetching from cache: 5", 25
console.log(square(7)); // "Calculating result for: 7", 49
console.log(square(7)); // "Fetching from cache: 7", 49
  1. memoizeSquare 함수가 실행되고, 내부 함수 square를 반환하며, cache 객체가 생성됩니다. 이 cache 객체는 이전 계산 결과를 저장하는 역할을 합니다.
  2. square 함수에 매개변수로 5가 전달됩니다.
  3. cache에 결과가 이미 있으면 해당 값을 반환하고, 없으면 새로 계산한 결과를 cache에 저장한 후 반환합니다.
  4. memoizeSquare 함수는 실행된 후 종료되지만, square 함수가 cache 객체를 참조하고 있으므로, 메모리에서 삭제되지 않고 남아 있습니다. 이는 내부 함수가 외부 함수의 변수를 기억하는 클로저로 작동하기 때문입니다.

배운 점

  1. 신기하게 클로저를 배우고, 오히려 실행 컨텍스트 작동 원리가 더 이해됨.
  2. 맨 처음 생성 과정에서 생긴 변수에 클로저에서 접근하는 것과 연관을 지으니 이해가 쉬움.
  3. 클로저를 사용할 일은 많지 않겠지만 실행 컨텍스트와 스코프를 공부하기엔 적격이다.
profile
천천히 가더라도 즐겁게 ☁

0개의 댓글

관련 채용 정보