: 스코프란 자바스크립트 엔진이 참조의 대상이 되는 식별자(Identifier)를 검색할 때 사용하는 규칙의 집합 이다. 즉, 어떤 변수를 사용하거나 함수를 호출하려고 할 때 해당하는 식별자로 사용하는데, 그 식별자를 검색하는 메커니즘이라고 이해하면 된다.
좀 더 근본적으로 Scope
를 우리말로 번역하면 범위
라는 뜻을 가지고 있다.
즉, 스코프(Scope)
란 변수에 접근할 수 있는 범위라고 할 수 있다.
var x = "global";
function foo() {
var x = "function scope";
console.log(x);
}
foo(); // ?
console.log(x); // ?
위와 같은 코드를 보고 어떤 결과가 나올까를 생각해보자🤔
이름이 같은 변수 x가 중복 선언되었다. 전역에서 변수 x를 참조할 때, 그리고 함수 foo 내부에서 변수 x를 참조할 때 이름이 중복된 2개의 변수 중 어떤 변수를 참조해야 하는가?
자바스크립트는 어떻게 변수를 식별하는 것일까?
스코프는 참조 대상 식별자(identifier, 변수, 함수의 이름과 같이 어떤 대상을 다른 대상과 구분하여 식별할 수 있는 유일한 이름)를 찾아내기 위한 규칙 이라는 설명이 좀 더 이해가 갈 것이다.
자바스크립트의 스코프는 타 언어와는 다른 특징을 가지고 있다.
대부분의 C-family language(C언어 기반)는 블록 레벨 스코프(block-level scope)를 따른다.
하지만 자바스크립트는 함수 레벨 스코프(function-level scope)를 따른다.
함수 레벨 스코프란 함수 코드 블록 내에서 선언된 변수는 함수 코드 블록 내에서만 유효하고 함수 외부에서는 유효하지 않다.
var x = 0;
{
var x = 1;
console.log(x); // 1 함수
}
console.log(x); // 1 전역
let y = 0;
{
let y = 1;
console.log(y); // 1 함수
}
console.log(y); // 0 전역
if (true) {
var x = 5;
}
console.log(x);
변수 x
는 코드 블록 내에서 선언되었다. 하지만 자바스크립트는 블록 레벨 스코프
를 사용하지 않으므로
함수 밖에서 선언된 변수는 코드 블록 내에서 선언되었다할지라도 모두 전역 스코프
을 갖게된다.
따라서 변수 x는 전역 변수이다.
단, ECMAScript 6에서 도입된 let
keyword를 사용하면 블록 레벨 스코프
를 사용할 수 있다.
for (let i = 0; i < 5; i++) {
console.log(i);
}
// 블록 스코프 안에서 정의된 변수 i는 블록 범위를 벗어나는 즉시 접근할 수 없다.
console.log("final i:", i); // ReferenceError
함수 스코프
만 따른다. (단, 화살표 함수의 블록 스코프는 무시하지 않는다)var foo = function () {
var a = 3,
b = 5;
var bar = function () {
var b = 7,
c = 11;
// 이 시점에서 a는 3, b는 7, c는 11
a += b + c;
// 이 시점에서 a는 21, b는 7, c는 11
};
// 이 시점에서 a는 3, b는 5, c는 not defined
bar();
// 이 시점에서 a는 21, b는 5
};
전역 스코프(Global Scope)는 말 그대로 전역에 선언되어있어 어느 곳에서든지 해당 변수에 접근할 수 있다,
전역 변수 (Global variable)는 전역에서 선언된 변수이며 어디에든 참조할 수 있다.
지역 스코프(Local Scope)는 해당 지역에서만 접근할 수 있어 지역을 벗어난 곳에선 접근할 수 없다,
지역 변수 (Local variable)는 지역(함수) 내에서 선언된 변수이며 그 지역과 그 지역의 하부 지역에서만 참조할 수 있다.
전역 스코프, 지역(함수) 스코프를 더 자세히 알아보자
var a = 1; // 전역 스코프
function print() {
// 지역(함수) 스코프
var a = 111;
console.log(a);
}
print(); // 111
console.log(a); // 1
print함수
를 호출하면 console엔 1
이 출력될까요? 아니면 111
이 출력될까?
print함수
를 호출하면111
이 출력되는 것을 볼 수 있다.
print 함수에서 console.log(a);
는 a
를 출력하기 위해 자신의 함수 스코프 안에 변수 a
가 있는지 찾아볼 거다.
그러면 var a = 111;
을 찾아내면 111
을 console에 출력하고 함수는 자신의 사명을 다하게 된다.
자바스크립트는 타 언어와는 달리 특별한 시작점(Entry point)이 없어서 위 코드와 같이 전역에 변수나 함수를 선언하기 쉽다.
따라서 전역에 변수를 선언하기 쉬우며 이것는 전역 변수를 남발하게 하는 문제를 야기시킨다.
전역 변수의 사용은 변수 이름이 중복될 수 있고, 의도치 않은 재할당에 의한 상태 변화로 코드를 예측하기 어렵게 만드므로 사용을 억제하여야 한다
렉시컬 스코프는 함수를 어디서 호출하는지가 아니라 어디에 선언하였는지에 따라 결정된다.
자바스크립트는 렉시컬 스코프를 따르므로 함수를 선언한 시점에 상위 스코프가 결정된다.
함수를 어디에서 호출하였는지는 스코프 결정에 아무런 의미를 주지 않는다.
var x = 1;
function foo() {
var x = 10;
bar();
}
function bar() {
console.log(x);
}
foo(); // ?
bar(); // ?
예제의 함수 bar
는 전역에 선언되었다. 따라서 함수 bar
의 상위 스코프는 전역 스코프이고 위 예제는 전역 변수 x
의 값 1을 두번 출력한다.
var x = 10; // 전역 변수
function foo() {
// 선언하지 않은 식별자
y = 20;
console.log(x + y);
}
foo(); // 30
y
는 선언하지 않은 식별자, 따라서 y = 20
이 실행되면 참조 에러가 발생할 것처럼 보인다.
하지만 선언하지 않은 식별자 y
는 마치 선언된 변수처럼 동작한다. 이는 선언하지 않은 식별자에 값을 할당하면 전역 객체의 프로퍼티가 되기 때문이다. (전역변수가 된다.)
foo
함수가 호출되면 자바스크립트 엔진은 변수 y
에 값을 할당하기 위해 먼저 스코프 체인을 통해 선언된 변수인지 확인.
이때 foo
함수의 스코프와 전역 스코프 어디에서도 변수 y의 선언을 찾을 수 없으므로 참조 에러가 발생해야 하지만, 자바스크립트 엔진은 y = 20
을 window.y = 20
으로 해석.
결국 y
는 전역 객체의 프로퍼티가 되어 마치 전역 변수처럼 동작
이러한 현상을 암묵적 전역(implicit global)이라 한다.
하지만 y는 변수 선언없이 단지 전역 객체의 프로퍼티로 추가되었을 뿐이다. 따라서 y는 변수가 아니다. 따라서 변수가 아닌 y는 변수 호이스팅이 발생하지 않는다. (아래 예제 확인)
// 전역 변수 x는 호이스팅이 발생한다.
console.log(x); // undefined
// 전역 변수가 아니라 단지 전역 프로퍼티인 y는 호이스팅이 발생하지 않는다.
console.log(y); // ReferenceError: y is not defined
var x = 10; // 전역 변수
function foo() {
// 선언하지 않은 변수
y = 20;
console.log(x + y);
}
foo(); // 30
현재 스코프에서 식별자를 검색할 때 상위 스코프를 연쇄적으로 찾아나가는 방식,
실행컨텍스트의 개념을 알아야한다.
LexicalEnvironment
의 EnvironmentRecord
에서 식별자를 검색한다.outer
참조 값으로 스코프 체인을 타고 올라가 상위 스코프의 EnvironmentRecord
에서 식별자를 검색한다.outer
참조 값이 null
일 때까지 계속하고 찾지 못한다면 에러를 발생시킨다.