let 키워드의 특징입니다.
대부분의 프로그래밍 언어는 블록 레벨 스코프를 따르지만 자바스크립트는 함수 레벨 스코프를 따릅니다. 하지만 let 키워드로 선언된 변수는 블록 레벨 스코프를 따릅니다.
아래 예제에서 코드 블록 내에 선언된 변수 foo는 블록 레벨 스코프를 갖는 지역 변수입니다. 전역에서 선언된 변수 foo와는 다른 별개의 변수입니다.
또한 변수 bar도 블록 레벨 스코프를 갖는 지역 변수입니다. 따라서 전역에서는 변수 bar를 참조할 수 없습니다.
let foo = 123; // 전역변수
{
let foo = 456; // 지역변수
let bar = 456; // 지역변수
}
console.log(foo); // 123
console.log(bar); // ReferenceError:bar is not defined
var 키워드로는 동일한 이름을 갖는 변수를 중복해서 선언할 수 있었습니다. 하지만 let 키워드로는 동일한 이름을 갖는 변수를 중복해서 선언할 수 없습니다.
var foo = 123;
var foo = 456; //중복 선언 허용
let foo = 123;
let foo = 456; // Uncaught SyntaxError: Identifier 'bar' has already been declared
var 키워드로 선언된 변수와는 달리 let 키워드로 선언된 변수를 선언문 이전에 참조하면 참조에러(ReferenceError)가 발생합니다. 이는 let으로 선언된 변수는 스코프의 시작에서 변수의 선언까지 일시적 사각지대(Temporal Dead Zone)에 빠지기 때문입니다.
console.log(foo); // undefined
var foo;
console.log(bar) //Error:Uncaught ReferenceError:bar is not defined
let bar;
let 키워드로 선언된 변수는 선언 단계와 초기화 단계가 분리되어 진행됩니다.(var는 동시에 진행) 즉, 스코프에 변수를 등록(선언단계)하지만 초기화 단계는 변수 선언문에 도달했을 때 이루어집니다. 초기화 이전에 변수에 접근하려고 하면 참조에러(ReferenceError)가 발생합니다. 이는 변수가 아직 초기화되지 않았기 때문입니다. 다시 말하면 변수를 위한 메모리 공간이 아직 확보되지 않았다는 뜻입니다. 따라서 스코프의 시작 지점부터 초기화 시작 지점까지는 변수를 참조할 수 없습니다. 그 구간을 '일시적 사각지대(Temporal Dead Zone)'라고 부릅니다.
//스코프의 선두에서 선언 단계 실행
//변수 초기화는 아직(메모리 공간 확보와 undefined로 초기화)
//따라서 변수 선언문 이전에 변수 참조 불가
console.log(foo); // ReferenceError:bar is not defined
let foo; // 변수 선언문에서 초기화 단계 실행
console.log(foo); // undefined
foo = 1; // 할당문에서 할당 단계 실행
console.log(foo); //1
결국 ES6에서는 호이스팅이 발생하지 않는 것과 차이가 없어 보이지만 그렇지 않습니다.
let foo = 1; // 전역 변수
{
console.log(foo); // ReferenceError: foo is not defined
let foo = 2; // 지역 변수
}
위 예제의 경우, 전역 변수 foo의 값이 출력될 것처럼 보입니다. 하지만 ES6의 선언문도 여전히 호이스팅이 발생하기 때문에 참조 에러(ReferenceError)가 발생합니다.
ES6의 let으로 선언된 변수는 블록 레벨 스코프를 가지므로 코드 블록 내에서 선언된 변수 foo는 지역 변수입니다. 따라서 지역 변수 foo도 해당 스코프에서 호이스팅되고 코드 블록의 선두부터 초기화가 이루어지는 지점까지 일시적 사각지대(TDZ)에 빠집니다. 따라서 전역 변수 foo의 값이 출력되지 않고 참조 에러(ReferenceError)가 발생합니다.
블록 레벨 스코프를 지원하는 let은 var보다 직관적입니다.
var func = [];
// 함수의 배열을 생성하는 for 루프의 i는 전역 변수이다.
for(var i=0; i<3; i++){
func.push(function(){
console.log(i)
});
}
// 배열에서 함수를 꺼내어 호출한다.
for(var j=0; j<3; j++){
func[j]();
}
위 코드의 실행 결과로 0,1,2를 기대할 수도 있지만 결과는 3이 세 번 출력된다. 그 이유는 for 루프의 var i가 전역 변수이기 때문이다. 0,1,2를 출력하려면 아래와 같은 코드가 필요하다.
var func = [];
// 함수의 배열을 생성하는 for 루프의 i는 전역 변수이다.
for(var i=0; i<3; i++){
(function(index){
func.push(function(){
console.log(index)
});
}(i));
}
// 배열에서 함수를 꺼내어 호출한다.
for(var j=0; j<3; j++){
func[j]();
}
자바스크립트의 함수 레벨 스코프로 인하며 for 루프의 초기화 식에 사용된 변수가 전역 스코프를 갖게 되어 발생하는 문제를 회피하기 위해 클로저를 활용한 방법입니다.
ES6의 let 키워드를 for 루프의 초기화 식에 사용하면 클로저를 사용하지 않아도 위 코드와 동일한 동작을 합니다.
var func = [];
for(let i=0; i<3; i++){
func.push(function(){
console.log(i)
});
}
// 배열에서 함수를 꺼내어 호출한다.
for(var j=0; j<3; j++){
console.dir(func[j]);
func[j]();
}
여기서 i는 for loop에서만 유효한 지역 변수입니다.
또한 i는 자유 변수로서 for loop의 생명주기가 종료되어도 변수 i를 참조하는 함수가 존재하는 한 계속 유지됩니다.
전역 객체(Global Object)는 모든 객체의 유일한 최상위 객체를 의미하며 일반적으로 Browser-side에서는 window 객체, Server-side(Node.js)에서는 global 객체를 의미합니다. var 키워드로 선언된 변수를 전역 변수로 사용하면 전역 객체의 프로퍼티가 됩니다.
var foo = 123; // 전역변수
console.log(window.foo); // 123
let 키워드로 선언된 변수를 전역 변수로 사용하는 경우, let 전역 변수는 전역 객체의 프로퍼티가 아닙니다. 즉, window.foo와 같이 접근할 수 없습니다. let 전역 변수는 보이지 않는 개념적인 블록 내에 존재하게 됩니다.
let foo = 123; // 전역변수
console.log(window.foo); // undefined