[JS] 스코프와 스코프체인
《 실행 컨텍스트 》과 연관된 내용이 많으므로 같이 공부하도록 하자!
▶ scope
스코프(scope)란?
- 모든 식별자는 자신이 선언된 위치에 다른 코드가 식별자 자신을 참조할 수 있는 범위가 결정된다.
- 즉, 스코프(유효 범위)는 식별자가 어디까지 유효한지 범위를 결정한다.
- 식별자는 변수이름이나 함수 이름이다. 즉, 어떤 값을 구별하여 식별해낼수 있는 고유한 이름을 말한다.
예시
- 아래 코드를 보면
var x
가 함수 밖(전역 변수) 에서 1번 함수 안(지역 변수)에서 1번 참조된 것을 알 수 있다.
var x = "global";
function test() {
var x = "local";
console.log(x); // "local";
}
test();
console.log(x); // "global";
➡️ 하지만, var x
가 어디있는지에 따라 동일한 코드도 다른 결과를 만들어 낸다.
➡️ 그 이유는, 이름은 동일한 식별자이지만 스코프가 다르기 때문이다.
➡️ 즉, 위의 코드를 통해 다른 스코프에서는 같은 이름의 식별자를 사용할 수 있다는 것을 알 수 있다.
스코프가 없으면 어떻게 될까?
스코프라는 개념이 없다면 같은 이름을 갖는 변수는 충돌을 일으킬 것이다.
▷ scope 종류
변수가 어느 위치에 선언되어있는지에 따라 Global Scope(전역 스코프)와 local Scope(지역 스코프)로 나뉜다.
// Global Scope
var x = "global";
// local Scope
function test() {
var x = "local";
console.log(x); //1
}
① Global Scope
전역범위에서 변수 선언시 어디서든지 접근할 수 있다.
// global scope에서의 변수 선언
var varvalue = "Happy!";
let letvalue = "Happy!";
const constvalue = "Happy!";
// 일반적인 접근
console.log(varvalue);
console.log(letvalue);
console.log(constvalue);
// 함수에서의 접근
function print_value() {
console.log(varvalue);
console.log(letvalue);
console.log(constvalue);
}
print_value();
// 조건문에서의 접근
if (true) {
console.log(varvalue);
console.log(letvalue);
console.log(constvalue);
}
② local Scope
함수 범위에서 변수 선언시 해당 함수 내부에서만 접근 가능하다.
*lacal(지역)이란 함수 본체 내부를 말한다.
함수 외부에서 변수 값 접근 불가 -> (출력x)
// 함수 내부에서의 변수 선언
function print_Value() {
const value = "Happy!";
}
console.log(value);
아래처럼 함수 내에서만 변수 값 접근 가능 -> (출력o)
function print_Value() {
const value = "Happy!";
console.log(value);
}
print_Value();
③ function level scope
var 키워드로 선언된 변수는 오로지 함수의 코드 블록(if, for, while 등)만을 지역 스코프로 인정하는데, 이러한 특성을 함수 레벨 스코프라고 한다.
var x = 1;
if (true) {
var x = 10;
}
console.log(x); //10
- 따라서 위 코드는 1이 아닌 10이출력된다.
- var 키워드로 선언된 변수는 함수 레벨 스코프만 인정하기 때문에 전역 변수의 값이 재할당 되기 때문이다!
④ lexical scope
아래 코드의 실행 결과를 예측해보자
var x = 1;
function foo() {
var x = 10;
bar();
}
function bar() {
console.log(x);
}
foo();
bar();
위 코드의 실행 결과는 var 함수의 상위 스코프에 따라 정의된다.
- 함수를 어디서 호출했는지에 따라 상위 스코프를 결정한다. -> dynamic scope(동적 스코프)
- 함수를 어디서 정의했는지에 따라 상위 스코프를 결정한다. -> lexical scope(정적 스코프)
⭐ JS 엔진은 함수를 어디서 “호출” 했는지가 아니라, 어디에 “정의” 했는지에 따라서 상위 스코프를 결정하는데, 이를 렉시컬 스코프(lexical scope)라고 한다.
- 렉시컬 스코프의 개념을 배웠으니 위 코드를 다시 보자.
- 위 코드에서
bar
함수가 호출되면 호출된 시점과는 관계없이 정의된 시점에서의 스코프에 의해 결정되므로 자신이 기억하고 있는 전역 스코프를 상위 스코프로 사용한다. 따라서 위 코드를 실행하면 변수 x의 값 1이 두 번 출력된다.
▶ scope chain
- 코드를 다시 한 번 살펴보자, js엔진은 어떻게 같은 변수명을 가진 x를 구별해 내어 다른 결과를 출력할 수 있었을까?
var x = "global";
function test() {
var x = "local";
console.log(x); // "local";
}
test();
console.log(x); // "global";
➡️ js 엔진이 스코프 체인을 사용하여 동일한 변수명을 가진 변수를 구별하였기 때문에 다른 결과를 출력할 수 있었던 것이다.
스코프 체인이란?
- 식별자의 유효범위를 안에서부터 바깥으로 차례로 검색해나가는 것을 말한다.
- 스코프 체인은 실행 컨텍스트의 렉시컬 환경(LE)을 단방향으로 연결(Chaining)한 것이다.
- 즉, 렉시컬 환경은 스코프 엔진의 실체라고 할 수 있다!!
-
변수를 참조할 때 스코프 체인을 통해 변수를 참조하는 코드의 스코프에서 시작하여 상위 스코프 방향으로 이동하며 선언된 함수를 검색하는데, 이를 통해 동일한 변수 명이라도 스코프에 따라서 변수를 구별할 수 있는 것이다.
-
따라서 상위 스코프에서 유효한 변수는 하위 스코프를 참조할 수 없다!!
함수의 스코프와 스코프 체인
const globalVar = "전역 변수"; // 전역 스코프에 선언된 변수
function outerFunction() {
const outerVar = "외부 함수의 변수"; // outerFunction의 스코프에 선언된 변수
function innerFunction() {
const innerVar = "내부 함수의 변수"; // innerFunction의 스코프에 선언된 변수
// 여기서는 모든 상위 스코프의 변수에 접근할 수 있음
console.log(globalVar); // "전역 변수"
console.log(outerVar); // "외부 함수의 변수"
console.log(innerVar); // "내부 함수의 변수"
}
innerFunction();
// outerFunction 스코프에서는 innerFunction의 변수에 접근할 수 없음
console.log(globalVar); // "전역 변수"
console.log(outerVar); // "외부 함수의 변수"
// console.log(innerVar); // ReferenceError: innerVar is not defined
}
outerFunction();
// 전역 스코프에서는 outerFunction 또는 innerFunction의 변수에 접근할 수 없음
console.log(globalVar); // "전역 변수"
// console.log(outerVar); // ReferenceError: outerVar is not defined
// console.log(innerVar); // ReferenceError: innerVar is not defined
스코프 | 설명 |
---|---|
전역 스코프 | globalVar 는 프로그램의 어디서나 접근 가능한 전역 변수이다. |
함수 스코프 | outerVar 는 outerFunction 함수 내에서만 접근 가능하며, innerVar 는 innerFunction 함수 내에서만 접근 가능하다. |
스코프 체인 | innerFunction 은 자신의 스코프, outerFunction 의 스코프, 그리고 전역 스코프에 있는 변수에 접근할 수 있다. 이것이 스코프 체인의 개념이다. |
스코프 제한 | 각 함수 외부에서는 해당 함수의 스코프 내 변수에 접근할 수 없다. 예를 들어, outerFunction 밖에서는 outerVar 에 접근할 수 없으며, 전역 스코프에서는 어떤 함수의 지역 변수에도 접근할 수 없다. |
스코프 참조 자세한 과정
아래 그림이 이해가 안가면 《 호이스팅 》을 보고 오자.
- ‘A’라는 실행 컨텍스트가 생성될 때의 환경은 전역 컨텍스트이므로, LE를 참조하게 되어 그 당시의 환경 정보를 저장하게 된다.
- ‘B’라는 실행 컨텍스트가 생성될 때, 그때의 환경은 ‘A’이므로, ‘A’의 LE를 ‘B’의 outer로 가지고 있는다.
- ‘C’라는 실행 컨텍스트가 생성될 때, 그때의 환경은 ‘B’이므로, ‘B’의 LE를 ‘C’의 outer로 가지고 있는다.
- 그때의 변수 정보 등을 참조하기 위해 가지고 있는것
➡️ 이 과정을 스코프 체이닝이라고 한다.
정리
-
outer는 현재 호출된 함수가 선언될 당시의 LE를 참조(= 그 당시의 환경 정보를 저장)한다.
- 예를 들어,
A함수 내부에 B함수 선언
→B함수 내부에 C함수 선언(Linked List)
한 경우 - 결국 타고, 타고 올라가다 보면 전역 컨텍스트의 LE를 참조하게 된다.
- 항상 outer는 오직 자신이 선언된 시점의 LE를 참조하고 있으므로, 가장 가까운 요소부터 차례대로 접근이 가능하다.
➡️ 결론 : 무조건 스코프 체인 상에서 가장 먼저 발견된 식별자에게만 접근이 가능하다.
예시
var a = 1;
var outer = function () {
var inner = function () {
console.log(a); // 1 : 이 값은 뭐가 나올지 예상해보자
var a = 3;
};
inner();
console.log(a); // 2 : 이 값은 뭐가 나올지 예상해보자
};
outer();
console.log(a); // 3 : 이 값은 뭐가 나올지 예상해보자
- 호이스팅 전
var inner = function () {
console.log(a); // 1
var a = 3;
};
inner();
- 호이스팅 후
var inner = function () {
var a; // 선언이 호이스팅됨
console.log(a); // 1 : undefined, 초기화는 호이스팅되지 않음
a = 3; // 할당
};
inner();
2 : console.log(a);
는 전역 영역인 var a =1
을 가져오므로 1이 출력된다.
3 : console.log(a);
은 전역 영역 내부 스코프의 var a =1
이 되므로 1이 출력된다.
정리⭐⭐
➡️ 각각의 실행 컨텍스트는 LE 안에 record와 outer를 가지고 있고, outer 안에는 그 실행 컨텍스트가 선언될 당시의 LE정보 (outer 입장에선 전역 context)가 다 들어있으니 scope chain에 의해 상위 컨텍스트의 record를 읽어올 수 있다.
댓글남기기