📚 cs

[JavaScript] 스코프 체인 (Scope Chain)

dev.daisy 2025. 8. 31. 15:33
스코프 체인을 공부하기 전에는 “변수를 찾을 때 안쪽에서 바깥쪽으로 찾는다” 정도만 이해했었는데, 실제로는
 Lexical Environmentouter 참조를 기반으로 엔진이 규칙적으로 동작한다는 것을 알게 되니 코드가 어떻게 실행되고 왜 특정 상황에서 에러가 발생하는지 명확하게 이해할 수 있었습니다.

특히 React의 Hook이 단순한 스코프 체인 원리에서 비롯되었다는 점이 신기했습니다. 앞으로는 단순히 “작동한다”를 넘어서, 엔진이 어떻게 탐색하고 연결하는지까지 설명할 수 있도록 더 깊이 이해해야겠다고 느꼈습니다.

 

스코프 체인(Scope Chain)이란?

 

스코프 체인은 자바스크립트에서 식별자(변수, 함수 등)를 탐색할 때 현재 스코프에 없으면 상위 스코프로 거슬러 올라가며 찾는 구조를 의미합니다. 쉽게 말해, 내부 스코프에서 변수를 못 찾으면 외부 스코프를 차례로 탐색하는 연결 관계라고 할 수 있습니다.

 

자바스크립트는 렉시컬 스코프(선언 위치 기준)를 따르기 때문에, 함수가 어디서 호출되었는지가 아니라 어디서 정의되었는지에 따라 스코프 체인이 결정됩니다.

 

자바스크립트에서 스코프 체인이란 무엇이며, 변수 탐색 시 어떤 방향으로 동작하는지 설명해주세요.

스코프 체인은 변수나 함수 등을 찾을 때 사용하며, 가장 가까운 스코프부터 탐색하며 내부 스코프에 식별자가 없을 경우 상위 스코프로 거슬로 올라가며 찾는 구조를 의미합니다. 변수 탐색 시 현재 스코프에서 변수를 못 찾으면 outer 참조를 따라 상위 스코프로 이동하고, 찾지 못할 경우 전역 스코프까지 올라갑니다. 만약 전역 스코프에서도 찾지 못할 경우에는 ReferenceError를 발생시킵니다.
자바스크립트에서 함수의 스코프가 호출 위치가 아닌 선언 위치를 기준으로 결정되는 이유는 무엇인가요? 

자바스크립트는 렉시컬 스코프(Lexical Scope) 규칙을 따르기 때문에 함수가 어디서 선언되었는지에 따라 외부 변수를 참조합니다. 따라서 호출 위치는 영향을 주지 않지만 동적 스코프를 사용하는 다른 언어는 함수가 어디서 호출되었는지에 따라 외부 변수를 참조합니다.

스코프 체인이 만들어내는 특징

1) 렉시컬 스코프 기반 탐색

  • 함수가 호출된 위치가 아니라 선언된 위치를 기준으로 외부 변수를 찾습니다.
  • 전역 → 지역 순서로 내려가는 것이 아니라, 지역 → 상위 지역 → 전역 순서로 거슬러 올라갑니다.
const x = "global";

function outer() {
  const x = "outer";
  function inner() {
    console.log(x);
  }
  return inner;
}

const fn = outer();
fn(); // "outer"

 

inner 함수는 outer 내부에서 정의되었기 때문에, 실행 위치가 전역이라도 outer 스코프를 따라갑니다.

2) Lexical Environment 연결

  • 스코프 체인은 내부적으로 Lexical Environment의 outer 참조를 따라가는 구조입니다.
  • 즉, 각 실행 컨텍스트가 자신의 바깥 환경을 참조하고 있어 체인처럼 연결됩니다.
function a() {
  let x = 10;
  function b() {
    let y = 20;
    console.log(x + y);
  }
  b();
}
a(); // 30

 

b 함수의 실행 컨텍스트는 자신이 선언된 a의 렉시컬 환경을 outer 참조로 저장하고 있어, x를 탐색할 수 있습니다.

 

outer 참조란?
현재 스코프가 바깥 스코프를 가리키는 포인터를 의미합니다. 모든 Lexical Environment는 자신의 바깥 환경을 가리키고 있는데, 이 연결 고리가 바로 스코프 체인입니다.
[ inner Lexical Environment ]
   ↑ outer 참조
[ outer Lexical Environment ]
   ↑ outer 참조
[ Global Lexical Environment ]

3) ReferenceError 처리

  • 체인의 가장 바깥(전역)까지 갔는데도 변수를 찾지 못하면 ReferenceError가 발생합니다.
function foo() {
  console.log(bar); 
}
foo(); // ReferenceError: bar is not defined
위 코드 실행 시 어떤 에러가 발생하며, 그 이유를 스코프 체인과 연관지어 설명해보세요.

스코프 체인 탐색이 현재 스코프(foo) → 전역 스코프까지 진행되었지만 bar라는 변수를 찾지 못했기 때문에 ReferenceError가 발생합니다.

4) 클로저(Closure)의 기반

  • 클로저가 외부 변수를 계속 참조할 수 있는 이유 역시 스코프 체인 덕분입니다.
  • 실행 컨텍스트가 끝나도 outer 참조가 유지되기 때문에 변수가 살아남습니다.
function counter() {
  let count = 0;
  return function () {
    return ++count;
  };
}

const c = counter();
console.log(c()); // 1
console.log(c()); // 2
클로저가 외부 변수를 참조할 수 있는 이유를 스코프 체인 관점에서 설명해주세요. 클로저가 메모리를 차지하는 방식과 관련된 이슈는 어떤 게 있을까요?

클로저는 함수가 선언될 때 생성된 렉시컬 환경(Lexical Environment)를 기억합니다. 따라서 실행 컨텍스트가 종료되어도 내부 함수가 외부 변수를 참조하고 있으면 해당 스코프 체인이 유지됩니다. 이 때문에 클로저는 외부 변수를 계속 참조할 수 있지만, 불필요한 변수까지 유지하면 메모리 누수가 발생할 수 있습니다.

(자세히)
1. 함수가 실행될 때 실행 컨텍스트가 만들어지고, 그 안에 Lexical Environment가 생성됩니다.
→ 여기에는 Environment Record(자신의 변수를 저장하는 공간), outer 참조(바깥 스코프에 대한 참조)가 들어있습니다.
2. 내부 함수가 외부 스코프에서 변수를 사용할 때, 엔진은 스코프 체인을 따라 outer 참조를 계속 탐색합니다.
3. 함수가 종료되어 실행 컨텍스트가 사라져도, 내부 함수가 외부 변수를 참조하는 한 그 변수들은 GC 대상에서 제외됩니다. outer 참조가 살이있기 때문에 메모리에 남아있습니다.

사용 예시

1) 변수 탐색

const a = "global";

function outer() {
  const b = "outer";

  function inner() {
    const c = "inner";
    console.log(a); // global (전역 스코프에서 탐색)
    console.log(b); // outer (바로 바깥 스코프에서 탐색)
    console.log(c); // inner (자신의 스코프에서 탐색)
  }

  inner();
}

outer();

 

→ inner 함수는 자신이 선언된 위치 기준으로, outer → global 순서대로 스코프 체인을 거슬러 올라가며 식별자를 탐색합니다.

 

2) 이벤트 핸들러에서 클로저 활용

function counterCreator() {
  let count = 0;

  return function() {
    count++;
    return count;
  };
}

const counter = counterCreator();
console.log(counter()); // 1
console.log(counter()); // 2

 

→ 반환된 내부 함수가 상위 스코프(count)를 계속 참조할 수 있는 이유가 스코프 체인입니다.

이 구조를 기반으로 React의 useState 같은 Hook도 동작하며, 함수형 프로그래밍에서 상태를 안전하게 보존할 수 있습니다.

 

3) React Hook 내부 동작

  • useState의 setter는 이전 상태를 클로저로 캡처해두기 때문에, 컴포넌트가 리렌더링되어도 상태가 유지됩니다.
  • 즉, 우리가 쓰는 setCount 같은 함수도 스코프 체인 기반으로 상태를 참조합니다.
React의 useState나 useEffect 같은 Hook이 스코프 체인과 어떤 관련이 있을까요? 예를 들어, useState의 setter가 상태를 어떻게 유지할 수 있는지 원리를 설명해보세요.

useStatesetter는 이전 상태를 클로저로 캡처해두기 때문에, 컴포넌트가 리렌더링되어도 그 상태에 접근할 수 있습니다. setter 함수가 생성될 당시의 스코프 체인을 따라가며 상태를 참조하기 때문에 값이 유지됩니다. 이 원리 덕분에 Hook은 함수형 컴포넌트에서도 상태를 안전하게 보존할 수 있습니다.

보조 개념 정리

  • 렉시컬 스코프(Lexical Scope): 함수가 정의된 위치를 기준으로 스코프가 결정되는 규칙입니다.
  • 실행 컨텍스트(Execution Context): 코드가 실행될 때 스코프, 변수, this 등의 정보를 담는 환경입니다.
  • Lexical Environment: 스코프 체인을 구성하는 핵심 자료구조로, 현재 스코프의 환경 레코드와 외부 스코프 참조(outer)를 포함합니다.
  • outer 참조: 현재 스코프가 바깥 스코프를 가리키는 포인터입니다.