자바스크립트 스코프(Scope)란? 렉시컬 스코프와 스코프 체인
스코프는 변수 같은 식별자를 참조할 수 있는 범위이며, 렉시컬 스코프는 코드가 작성된 위치에 따라 그 범위를 결정하는 규칙이다. 둘 다 여러 프로그래밍 언어에서 쓰이는 개념이며, 자바스크립트도 렉시컬 스코프를 따른다.
스코프(scope)란?
-
프로그래밍 언어에서 변수를 선언하면 그 변수는 코드의 정해진 범위 안에서만 참조할 수 있다.
-
다음은 함수 안에서 선언한 변수를 참조할 수 있는 범위를 보여주는 자바스크립트 예시이다.
function showMessage() { const message = "안녕하세요"; // showMessage 안에서 선언한 지역 변수 console.log(message); // 함수 안에서는 참조 가능 } showMessage(); // "안녕하세요" 출력 // ReferenceError가 발생한다. console.log(message); // 함수 밖에서는 이 지역 변수를 참조할 수 없다.
-
-
이렇게 식별자가 유효한 범위를 스코프라고 한다.
-
스코프가 있기 때문에 같은 이름의 변수가 서로 다른 범위에서 충돌 없이 존재할 수 있다.
-
이름이 같아도 서로 다른 스코프에서 선언하면 별개의 변수이다.
const score = 100; // 바깥 스코프의 score function showScore() { const score = 50; // 함수 안의 별도 변수: 바깥 score를 수정한 것이 아님 console.log(score); // 50: 현재 스코프의 score를 참조 } showScore(); console.log(score); // 100: 바깥 score는 그대로 유지됨
-
-
함수 안에서는 가까운 쪽의
score가 바깥쪽의 같은 이름을 가린다.
scope가 결정되는 기준
- 스코프를 정하는 방식은 크게 두 가지로 나뉜다.
- 실행 중인 호출 관계를 따라 호출자의 환경에서 식별자를 찾는 방식을 동적 스코프라고 한다.
- 함수를 어디서 정의했는지를 기준으로 정하는 방식을 렉시컬 스코프(정적 스코프)라고 한다.
- 자바스크립트는 렉시컬 스코프를 따른다.
- 즉, 함수의 상위 스코프는 코드를 작성한 위치에 따라 정해지고, 호출 위치와는 관계가 없다.
- 이해가 되지 않으면 아래 예시를 확인하고 다시 올라오자.
예시로 보는 동적 스코프 vs 렉시컬 스코프
const x = 1;
function foo() {
const x = 10;
bar(); // 호출된 위치: foo내부
}
// 정의된 위치: 전역
function bar() {
console.log(x);
}
foo(); // 1
bar(); // 1
-
bar는foo안에서 호출되었지만, 정의된 위치는 전역이다. -
그래서
bar의 상위 스코프는foo가 아니라 전역 스코프가 된다. 왜냐하면 어디서 정의했는지를 기준으로 정하는 방식이 렉시컬 스코프이기 때문이다. -
결과적으로
bar는 전역에 선언된x, 즉1을 참조한다.const x = 1; // 전역에 선언된 x는 요거 function foo() { const x = 10; // 이건 foo 내부에서 선언되었다. bar(); } // 전역에서 선언 function bar() { console.log(x); } foo(); bar(); // 1 -
만약 자바스크립트가 동적 스코프를 따랐다면
foo()를 호출했을 때10이 출력되었을 것이다. 왜냐하면 동적 스코프는 실행된 위치를 기준으로 식별자를 찾기 때문이다.const x = 1; function foo() { const x = 10; bar(); // 동적 스코프였으면 bar()는 바로 위에 있는 x = 10을 참조한다. } function bar() { console.log(x); } foo(); // 10: foo 안에서 호출됐으므로 foo의 x를 참조 bar(); // 1: 전역에서 호출됐으므로 전역 x를 참조
스코프 체인 (중첩된 함수)
-
함수 안에 함수를 정의하면 스코프도 겹겹이 중첩된다.
-
중첩된 스코프는 안쪽에서 바깥쪽으로 이어지는 연결 구조를 만든다. 이 연결 구조를 스코프 체인이라고 한다.
const a = 'global'; // 전역 변수 function outer() { const b = 'outer'; // outer 내부에서 선언 function inner() { const c = 'inner'; // outer -> inner 내부에서 선언 console.log(a, b, c); } inner(); } outer(); // global outer inner -
inner는 식별자를 찾을 때 먼저 자신의 스코프를 확인한다. -
찾지 못하면 한 단계 바깥인
outer의 스코프로 올라간다. -
거기서도 없으면 전역 스코프까지 올라가며 탐색을 이어간다.
-
이 탐색은 안쪽에서 바깥쪽으로 진행하므로, 바깥 코드가 안쪽 지역 변수를 그 이름으로 직접 참조할 수는 없다.
function outer() { const a = "바깥 변수"; function inner() { const b = "안쪽 변수"; console.log(a); // 가능: 안쪽에서 바깥 변수 참조 } inner(); console.log(b); // ReferenceError: 바깥에서 안쪽 변수 참조 불가 } outer();