JS var,let,const 스코프, 호이스팅
Block-level scope
대부분의 프로그래밍 언어는 Block-level scope를 따릅니다.
하지만 자바스크립트는 Function-level scope 를 따릅니다.
1
2
3
4
5
6
7
8
9
10
함수 레벨 스코프(Function-level scope)
함수 내에서 선언된 변수는 함수 내에서만 유효하며 함수 외부에서는 참조할 수 없다.
즉, 함수 내부에서 선언한 변수는 지역 변수이며 함수 외부에서 선언한 변수는 모두 전역 변수이다.
블록 레벨 스코프(Block-level scope)
모든 코드 블록(함수, 조건문, 반복문, 예외처리문 등) 내에서 선언된 변수는
코드 블록 내에서만 유효하며 코드 블록 외부에서는 참조할 수 없다.
즉, 코드 블록 내부에서 선언한 변수는 지역 변수이다.
1
2
3
4
5
6
7
var aaa = 111; <-- 전역 변수
console.log(aaa); <-- 실행 결과 111
{
var aaa = 222; <-- 전역 변수
}
console.log(aaa) <-- 실행 결과 222
블록 레벨 스코프를 따르지 않는 var 변수의 특성으로 인해 코드 블록 내의 변수 aaa는 전역 변수 입니다.
이미 맨 윗줄에 aaa가 선언되어 있습니다. var 변수로 선언된 변수는 중복선언이 가능하므로
위에 코드는 문법적으로 문제가 없습니다. 단, 코드 블록 내의 변수 aaa는 전역 변수이기 때문에 전역에서
전역에서 선언된 맨 위의 변수 aaa의 111을 새로운 값 222로 재할당 하여 덮어 씁니다.
이러한 문제를 해결 하기 위해 블록 레벨 스코프를 따르는 변수 let을 ES6에서 추가 되었습니다.
1
2
3
4
5
6
7
let aaa = 111; <-- 전역 변수
{
let aaa = 222; <-- 지역 변수
let bbb = 333; <-- 지역 변수
}
console.log(aaa); <-- 실행 결과 123
console.log(bbb); <-- 실행 결과 // ReferenceError: bbb is not defined
let 으로 선언된 변수는 블록 레벨 스코프에 따르기 때문에 코드 블럭 내에 선언된 변수 aaa는 블록 레벨
스코프를 갖는 지역 변수 입니다. 전역에서 선언된 변수 aaa와는 다른 별개의 변수 입니다.
또 변수 bbb도 블록 레벨 스코프를 가지고 있는 지역 변수 입니다. 따라서 전역에서는 변수 bbb를
참조 할 수 없는 ReferenceError가 발생합니다.
변수 중복 선언
var 문법으로 동일한 이름을 가지는 변수를 중복해서 선언할 수 있었습니다. 하지만 let 문법으로는
동일한 이름을 가지는 변수를 중복해서 선언 할 수 없습니다. 변수를 중복 선언 하면 문법 에러가 발생합니다.
1
2
3
4
5
6
var aaa = 111;
var aaa = 222; // 중복 선언 가능
let bbb = 111;
let bbb = 222;
// Uncaught SyntaxError: Identifier 'bbb' has already been declared
호이스팅
자바스크립트는 ES6에서 업데이트 된 let, const를 포함하여 모든 선언을 호이스팅 합니다.
호이스팅은 var 선언문이나 function 선언문을 해당 스코프의 맨 위로 옮긴 것처럼 동작하는 특성을 말합니다.
var 문법으로 선언된 변수와는 다르게 let 문법으로 선언된 변수를 선언문 이전에 참조하면
참조 에러(ReferenceError)가 발생합니다. let 으로 선언된 변수는 스코프의 시작에서 변수의 선언까지 일시적 사각지대(Temporal Dead Zone; TDZ)에 빠지기 때문입니다.
1
2
3
4
5
console.log(aaa); // undefined
var aaa;
console.log(bbb); // Error: Uncaught ReferenceError: bar is not defined
let bbb;
선언 단계(Declaration phase) 변수를 실행 컨텍스트의 변수 객체(Variable Object)에 등록한다.
이 변수 객체는 스코프가 참조하는 대상이 된다.
초기화 단계(Initialization phase)
변수 객체(Variable Object)에 등록된 변수를 위한 공간을 메모리에 확보한다.
이 단계에서 변수는 undefined로 초기화된다.
할당 단계(Assignment phase)
undefined로 초기화된 변수에 실제 값을 할당한다.
var 변수 호이스팅
var 로 선언된 변수는 선언 단계와 초기화 단계가 한번에 이루어 집니다.
스코프에 변수를 선언 하고 메모리에 변수를 위한 공간을 확보 한 뒤,undefined로 초기화 단계를 거칩니다.
따라서 변수 선언문 이전에 변수에 접근하여도 스코프에 변수가 존재하기 때문에 에러가 발생하지 않습니다.
다만 undefined를 반환 합니다. 이후 변수 할당문에 도달하면 그때서야 값이 할당 됩니다.
이러한 현상을 변수 호이스팅 이라고 합니다.
1
2
3
4
5
6
7
8
9
// 스코프의 선두에서 선언 단걔ㅖ와 초기화 단계가 실행된다.
// 따라서 변수 선언문 이전에 변수를 참조 할 수 있다.
console.log(aaa); // undefined
var aaa;
console.log(aaa) // undefined
aaa = 1; // 할당문에서 할당 단계가 실행 된다.
console.log(aaa) <-- 실행 결과 1
let 변수 호이스팅
let 으로 선언된 변수는 선언 단계와 초기화 단계가 분리되어 진행된다.
스코프에 변수를 선언 하지만 초기화 단계는 변수 선언문에 도달했을 때 이루어진다.
초기화 이전에 변수에 접근하려고 하면 하면 참조 에러(ReferenceError)가 발생한다.
이는 변수가 아직 초기화되지 않았기 때문이다. 간단하게 설명하면 변수를 위한 메모리 공간이 아직 확보 되지 않았기 때문이다.
1
2
3
4
5
6
7
8
9
// 스코프의 선두에서 선언 단계가 실행 된다.
// 아직 변수가 초기화 되지 않았다.
// 그러므로 변수 선언문 이전에 변수를 참조 할 수 없다.
console.log(aaa) // ReferenceError: foo is not defined
let aaa; // 변수 선언문에서 초기화 단계가 실행이 됩니다.
console.log(aaa) // undefined
aaa = 1; // 할당문에서 할당 단계가 실행 된다.
console.log(aaa) <-- 실행결과 1
let 에서는 호이스팅이 발생하지 않는 것처럼 보이지만 그렇지 않다.
1
2
3
4
5
let aaa = 1; // 전역 변수
{
console.log(aaa); // ReferenceError: foo is not defined
let aaa = 2; // 지역 변수
}
전역 변수 aaa의 값이 출력될 것처럼 보이지만 let 선언문도 여전히 호이스팅이 발생하기 때문에 참조 에러가 발생한다. let 으로 선언된 변수는 블록 레벨 스코프를 가지기 때문에 코드 블록 내에서 선언된 변수 aaa는 지역 변수입니다. 따라서 지역 변수 aaa도 해당 스코프에서 호이스팅이 일어나고 코드 블록의 선두부터 초기화가 일어나는 지점 까지 일시적 사각지대에 빠집니다. 그러므로 전역 변수의 aaa의 값이 출력 되지 않고 참조 에러가 발생합니다.
Const
const(상수) 는 변하지 않는 값을 위해 사용합니다. let은 재할당이 가능하지만 const는 재사용이 불가능 합니다. const는 반드시 선언과 동시에 할당이 이루어져야 합니다. 그렇지 않을 경우에는 문법에러가 발생합니다. const는 let과 마찬가지로 블록 레벨 스코프를 가지고 있습니다.
1
2
3
4
5
6
7
8
9
const aaa = 111;
aaa = 222; // TypeError: Assignment to constant variable.
const bbb; // SyntaxError: Missing initializer in const declaration
{
const bbb = 222;
console.log(bbb); // 222;
}
console.log(bbb); // // ReferenceError: bbb is not defined
const 와 객체
const는 재할당이 불가능 합니다. 하지만 const 변수의 타입이 객체일 경우 객체의 프로퍼티는 보호 되지 않습니다. 즉, const는 재할당이 불가능 하지만 할당된 객체의 내용은 변경할 수 있습니다.
1
2
3
const user = {name : 'kim'}
user.name = 'lee';
console.log(user); // { name: 'lee' }