1. 실행 컨텍스트와 렉시컬 환경

(1) 핵심 개념

**실행 컨텍스트(Execution Context)**는 javascript 코드가 평가되고 실행되는 환경의 추상적인 개념입니다. 모든 javascript 코드는 실행 컨텍스트 내에서 실행됩니다.

실행 컨텍스트는 다음과 같은 주요 구성 요소를 가집니다.

  1. 렉시컬 환경(Lexical Environment)
  2. 변수 환경(Variable Environment)
  3. This 바인딩

렉시컬 환경은 식별자와 식별자에 바인딩된 값, 그리고 외부 렉시컬 환경에 대한 참조를 포함하는 구조입니다. 렉시컬 환경은 다음 두 가지 구성 요소를 가집니다:

  1. 환경 레코드(Environment Record): 스코프에 포함된 식별자를 등록하고 식별자에 바인딩된 값을 관리합니다.
  2. 외부 렉시컬 환경에 대한 참조(Outer Lexical Environment Reference): 상위 스코프를 가리킵니다.

실행 컨텍스트의 생성 과정은 다음과 같습니다:

  1. 생성 단계(Creation Phase):
  2. 실행 단계(Execution Phase):

(2) 예시

graph TD
    %% 실행 컨텍스트 트리 구조
    Global[전역 실행 컨텍스트] --> Outer[outer 실행 컨텍스트]
    Outer --> Inner[inner 실행 컨텍스트]

    %% 전역 실행 컨텍스트 생명주기
    subgraph GlobalContext[전역 실행 컨텍스트 생명주기]
        G1[생성] --> G2[변수 x, y 초기화]
        G2 --> G3[outer 함수 정의]
        G3 --> G4[outer 함수 호출]
        G4 --> G5[대기]
        G5 --> G6[프로그램 종료]
    end

    %% outer 실행 컨텍스트 생명주기
    subgraph OuterContext[outer 실행 컨텍스트 생명주기]
        O1[생성] --> O2[변수 x 초기화]
        O2 --> O3[inner 함수 정의]
        O3 --> O4[inner 함수 호출]
        O4 --> O5[inner 함수 완료 대기]
        O5 --> O6[종료]
    end

    %% inner 실행 컨텍스트 생명주기
    subgraph InnerContext[inner 실행 컨텍스트 생명주기]
        I1[생성] --> I2[변수 y 초기화]
        I2 --> I3[console.log 실행]
        I3 --> I4[종료]
    end

    %% 컨텍스트 간 연결
    G4 -.-> O1
    O4 -.-> I1
    I4 -.-> O5
    O6 -.-> G5

    %% 스타일링
    classDef contextNode fill:#f9f,stroke:#333,stroke-width:2px;
    class Global,Outer,Inner contextNode;

    classDef globalStep fill:#bbf,stroke:#333,stroke-width:1px;
    class G1,G2,G3,G4,G5,G6 globalStep;

    classDef outerStep fill:#bfb,stroke:#333,stroke-width:1px;
    class O1,O2,O3,O4,O5,O6 outerStep;

    classDef innerStep fill:#ffb,stroke:#333,stroke-width:1px;
    class I1,I2,I3,I4 innerStep;

다음 코드를 통해 실행 컨텍스트와 렉시컬 환경의 동작을 살펴보겠습니다:

let x = 10;
let y = 20;

function outer() {
  let x = 100;

  function inner() {
    let y = 200;
    console.log(x + y);
  }

  inner();
}

outer();

이 코드의 실행 과정을 단계별로 설명하겠습니다

  1. 전역 실행 컨텍스트 생성:
  2. 전역 코드 실행:
  3. outer 함수 실행 컨텍스트 생성:
  4. outer 함수 코드 실행:
  5. inner 함수 실행 컨텍스트 생성:
  6. inner 함수 코드 실행:
  7. inner 함수 실행 종료, 실행 컨텍스트 제거
  8. outer 함수 실행 종료, 실행 컨텍스트 제거
  9. 전역 실행 종료

(3) 활용 사례 3가지

1) 모듈 패턴을 이용한 private 변수 구현

const createCounter = () => {
  // 클로저를 이용한 private 변수
  let count = 0;

  return {
    increment: () => {
      count++;
      console.log(`Count: ${count}`);
    },
    decrement: () => {
      count--;
      console.log(`Count: ${count}`);
    },
    getCount: () => count
  };
};

const counter = createCounter();
counter.increment(); // Count: 1
counter.increment(); // Count: 2
counter.decrement(); // Count: 1
console.log(counter.getCount()); // 1
console.log(counter.count); // undefined

이 예제에서 createCounter 함수의 렉시컬 환경은 count 변수를 포함합니다. 반환된 객체의 메서드들은 이 렉시컬 환경에 대한 클로저를 형성하여 count에 접근할 수 있지만, 외부에서는 직접 접근할 수 없습니다.

2) 커링(Currying)을 이용한 부분 적용 함수

function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    } else {
      return function(...args2) {
        return curried.apply(this, args.concat(args2));
      }
    }
  };
}

function add(a, b, c) {
  return a + b + c;
}

const curriedAdd = curry(add);

console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6
console.log(curriedAdd(1)(2, 3)); // 6

이 예제에서 curry 함수는 새로운 실행 컨텍스트를 생성하고, 그 안에서 curried 함수를 반환합니다. curried 함수는 자신의 렉시컬 환경에 fn과 이전에 전달받은 args를 유지하면서, 새로운 인자가 전달될 때마다 새로운 실행 컨텍스트를 생성합니다.

3) 이벤트 위임을 이용한 동적 엘리먼트 관리

class DynamicList {
  constructor(containerId) {
    this.container = document.getElementById(containerId);
    this.items = [];

    // 이벤트 리스너에서 this를 올바르게 바인딩
    this.handleClick = this.handleClick.bind(this);

    // 이벤트 위임
    this.container.addEventListener('click', this.handleClick);
  }

  addItem(text) {
    const id = Date.now();
    this.items.push({ id, text });
    this.render();
  }

  removeItem(id) {
    this.items = this.items.filter(item => item.id !== id);
    this.render();
  }

  handleClick(event) {
    if (event.target.classList.contains('remove-btn')) {
      const id = parseInt(event.target.dataset.id);
      this.removeItem(id);
    }
  }

  render() {
    this.container.innerHTML = this.items.map(item => `
      <div>
        ${item.text}
        <button class="remove-btn" data-id="${item.id}">Remove</button>
      </div>
    `).join('');
  }
}

const list = new DynamicList('list-container');
list.addItem('Item 1');
list.addItem('Item 2');