JavaScript를 공부하면서 Hoisting과 TDZ에 대한 이야기를 들어본 적이 있을 것이다.
이 Hoisting과 TDZ를 이해하기 위해서는 사용하고 있는 var
, let
,const
에 대해 잠시 살펴보고 넘어가겠다.
JavaScript에서 변수를 선언할 때 가장 많이 쓰이는 var에서 ES6에 let과 const가 추가되었다.
let과 const를 사용하기를 권장하는데 그 이유는 무엇일까?
다음 코드로 살펴보자
var person = 'jigom';
console.log( person );
// 출력 결과 : jigom
//----------- 10,000 라인 뒤 ------------//
var person = 10;
console.log( person );
// 출력 결과 : 10
이렇게 변수가 선언되어 있는데 중복으로 사용이 가능하다. 이렇게 변수 재선언을 막기 위해 나온 것이 바로 let
과 const
다.
let
은 변수를 중복 선언해서 사용할 수 없다.
let person = 'jigom';
console.log( person );
//----------- 10,000 라인 뒤 ------------//
let person = 10;
console.log( person );
// 출력 결과 : SyntaxError: Identifier 'person' has already been declared
const
도 변수를 중복 선언해서 사용할 수 없고, const
로 선언된 변수는 값을 수정할 수 없다.
const person = 'jigom';
console.log( person );
// 출력 결과 : jigom
//----------- 10,000 라인 뒤 ------------//
person = 10;
console.log( person );
// 출력 결과 : TypeError: Assignment to constant variable.
이외에 var
과 let
, const
는 변수 유효 범위와 Hoisting의 차이가 있다.
흔히 var
는 function-scoped라고도 표현한다.
다음 코드를 보자
function print(){
var test = 123;
console.log( test );
}
console.log( test );
// 출력 결과 : test is not defined
이렇게 함수 외부에서 호출하게 되면 에러를 발생한다.
하지만 if, else와 같이 block-scoped( { .. } 로 묶인 부분 )에서 실행하면 어떨까?
if( true ){
var test = 123;
console.log( test );
// 출력 결과 : 123
}
console.log( test );
// 출력 결과 : 123
이렇듯 var
는 function-scoped의 범위를 따른다.
그렇다면 block-scoped인 let
과 const
는 어떨까?
// let
function print(){
let test = 123;
console.log( test );
}
console.log( test );
if( true ){
console.log( test );
}
// const
function print(){
const test = 123;
console.log( test );
}
console.log( test );
if( true ){
console.log( test );
}
// 출력 결과 : test is not defined
{ }로 쌓여있는 모든 부분에서 접근할 수가 없다.
호이스팅(Hoisting)의 사전적 의미는 끌어 올리다 라는 뜻을 가지고 있다.
여기서도 같은 의미로 쓰인다. 함수 안에 있는 변수나 함수 맨위로 끌어올린다는 것이다.
실제로 코드가 끌어올려지는 것은 아니며, 자바스크립트가 내부적으로 끌어올려서 처리한다.
그럼 이런 경우는 어떨까? 한번 추측해보자
console.log(a); // undefined
a = 3;
console.log(a); // 3
var a = 1;
console.log(a); // 1
정상적으로 숫자를 출력한다. 그렇다면 let
과 const
는 어떨까?
// let
console.log(a); // ReferenceError: Cannot access 'a' before initialization
a = 3;
console.log(a); // ReferenceError: Cannot access 'a' before initialization
let a = 1;
console.log(a); // 출력 결과 : 1 ( 단, let a = 1 이전의 코드는 모두 주석처리 한다 )
//const
console.log(a); // ReferenceError: Cannot access 'a' before initialization
a = 3;
console.log(a); // ReferenceError: Cannot access 'a' before initialization
const a = 1;
console.log(a); // 출력 결과 : 1 ( 단, const a = 1 이전의 코드는 모두 주석처리 한다 )
그렇다면 let
과 const
는 Hoisting이 되지 않은걸까?
다음 예제를 보면 let
도 Hoisting이 된다는 것을 확인할 수 있다.
let a = 10;
{
console.log(a); // ReferenceError: Cannot access 'a' before initialization
let a = 20;
}
호이스팅이 안되는 것이라면 console.log(a) 에서 a가 상위 스코프의 a를 참조하여 10을 출력해야 하지만 ReferenceError가 난다는 것은 해당 스코프의 let a; 가 상위로 끌어 올려졌다는 것을 의미한다.
그럼 왜 let
과 const
는 ReferenceError를 나타낼 수 있는 것일까?
그것은 바로 TDZ( Temporal Dead Zone ) 때문이다.
이 TDZ를 설명하기 위해서는 간단히 변수 라이프 사이클에 대해 살펴보아야 한다.
변수는 크게 1. 선언단계 2. 초기화 단계 3. 할당 단계로 나뉜다.
이런 이유로 코드에 대한 안정성을 두고자 let
과 const
를 사용해야 한다.
아직 이 부분은 지식이 얕다. 이는 자료 조사를 통해 내용을 가져왔다.
스코프 체인(scope chain)이란?
스코프 체인(Scope Chain)은 일종의 리스트로서 전역 객체와 중첩된 함수의 스코프의 레퍼런스를 차례로 저장하고, 의미 그대로 각각의 스코프가 어떻게 연결(chain)되고 있는지 보여주는 것을 말한다.
하지만 스코프 체인(scope chain)을 이해하기 위해서 먼저 자바스크립트의 실행 컨텍스트(Execution context)를 알아야 한다.
function multiply(x, y) {
return x * y;
}
function printSquare(x) {
var s = multiply(x, x);
console.log(s);
}
printSquare(5);
이 코드가 실행될 때 콜 스택의 동작 단계는 다음과 같다.
만약 이렇게 재귀함수를 부르게 된다면
function foo {
foo();
}
foo();
스택 오버플로우가 생겨 프로그램이 강제 종료 된다.
var v = "전역 변수";
function a() {
//function a Execution Context(EC)
var v = "지역 변수";
function b() {
//function b Execution Context
console.log(v);
}
b();
}
//Global Execution Context
a();
위 코드의 예제를 보면 먼저 글로벌 실행 컨텍스트(GEC)가 실행되고 스택에 쌓인다.
그런 다음 함수 호출 순으로 실행 컨텍스트 스택에 쌓이게 되고, 가장 나중에 호출된 b() 함수가 실행 컨텍스트 안에서부터 탐색을 시작한다.
그러면, b() 함수 안에서 변수 v를 탐색하기 시작하는데, 만약 변수 v가 없으면 b() 함수를 감싸고 있는 외부 함수 a() 함수를 탐색하기 시작한다.
이때 a() 함수 안에 변수 v가 존재하면 안에 있는 v를 참조하게 되고, 만약 없다면 마지막으로 전역 객체를 탐색하여 v를 찾아낸다.
반대로 찾았다면, 결과값은 a() 안에 변수 v가 존재하기 때문에 지역 변수라는 값이 출력이 된다.
하지만 만약, a() 함수 안에 변수 v를 제거한다면 전역 객체에 있는 변수 v의 값 전연 변수가 출력이 될 것이다.
이러한 과정들이 스코프에 담긴 순서대로 탐색하는 즉, 스코프 체인이라고 보면 된다.
[ 참고 링크 - 태기의 개발 Blog ]
1: let b = 1;
2:
3: function hi () {
4:
5: const a = 1;
6:
7: let b = 100;
8:
9: b++;
10:
11: console.log(a,b);
12:
13:}
14:
15://console.log(a);
16:
17:console.log(b);
18:
19:hi();
20:
21:console.log(b);
다음과 같은 코드가 있다고 하자.
주석을 처리하지 않은 상태에서 출력은 어떻게 나올까?
출력 결과
17번 라인 : 1 출력
19번 라인 : 1, 101출력
21번 라인 : 1 출력
let b = 1
을 참조한다.let b = 1
의 전역을 보지 않고 함수 내부에 동일한 변수명으로 선언된 let b = 100
를 참조하기 때문에 console.log( a, b )는 1과 101을 출력한다.만약 15번 라인의 주석 처리를 없애면 어떨까?
a를 찾아보면 5번 라인의 const
로 선언되어 있다.
const
는 block-scope 단위이기 때문에, 5번 라인의 a 변수를 참조하지 못한다.
따라서, a is not defined
가 출력된다.