함수 선언은 아래 줄에 있지만 첫 번째 줄에서 식별자에 접근 가능
선언은 스코프 아래에 있더라도 스코프 시작 부분에서 변수의 가시성(visibility)이 확보되는 것이 호이스팅(hoisting)
함수 선언문에는 고유한 특성인 함수 호이스팅이 있어 함수 선언문으로 함수를 선언하면,
함수 이름에 해당하는 식별자가 스코프 최상단에 등록되고 함수 참조로 그 값이 자동으로 초기화
따라서 함수 선언문은 함수 호이스팅에 의해 스코프 내 어디서든 호출 가능
함수 호이스팅과 var를 사용해 선언한 변수의 호이스팅 모두에서 이름 식별자가 블록 스코프가 아닌 가장 가까운 함수 스코프에 등록
가장 가까운 함수 스코프가 없으면 전역 스코프에 등록
let과 const로 변수를 선언해도 호이스팅이 일어남
하지만 let과 const로 선언한 변수는 var로 선언한 변수와 함수 선언문과 다르게 식별자가 함수 스코프가 아닌 블록 스코프에 등록
함수 호이스팅은 일반적인 함수 선언문에만 적용되고 함수 표현식에는 적용되지 않음
var로 선언한 변수는 호이스팅도 되고 여기에 더해 스코프(가장 가까운 함수 스코프 혹은 전역 스코프)가 시작될 때 undefined로 자동 초기화
초기화 이후에는 스코프 전체에서 이 변수를 사용할 수 있음(할당, 참조 등)
함수 선언문은 호이스팅되고 해당 함숫값으로 초기화 (= 함수 호이스팅)
반면 var로 선언한 변수는 호이스팅되긴 하지만 undefined로 초기화
함수 표현식에서 실제 할당은 런타임에 해당 코드가 실행되기 전까지는 일어나지 않음
선언문과 표현식 모두에서 이름 식별자는 호이스팅된다는 공통점이 있음
하지만 초기화(스코프 시작)시 함수 선언문이 아닌 경우 함수 참조 관련 작업이 처리되지 않음
greeting = '안녕하세요'; console.log(greeting); // 안녕하세요! var greeting = '안녕!';
네 번째 줄에서 greeting을 선언했지만, 첫 번째 줄에서부터 greeting에 값을 할당 가능
식별자가 호이스팅되고, 스코프 최상단에서 식별자가 undefined로 자동 초기화 되기 때문
호이스팅은 자바스크립트 엔진이 실행되는 과정 중의 일부가 아니라 프로그램이 실행되기 전 JS가 수행하는 여러 행동을 시각화하는 도구
무거운 물건을 위로 들어 올리는 리프팅처럼 호이스팅을 거치면 모든 식별자가 스코프 맨 위쪽으로 올라간다는 생각으로 호이스팅을 리프팅에 비유함
이러한 비유를 적용하면 호이스팅을 거친 코드는 JS 엔진에 의해 전처리되고, 그 과정에서 모든 선언이 재정렬을 거쳐 실행 전에 해당 스코프의 최상단으로 이동되며 함수 선언문에도 동일하게 적용
리프팅에 비유한 호이스팅에서는 함수 선언문 모두를 먼저 호이스팅하고 그 다음 변수를 호이스팅한다는 규칙이 있음
이렇게 비유를 사용해 호이스팅을 이해하면 코드를 쉽게 이해할 수 있음
호이스팅을 코드 재정렬 메커니즘이라고 생각하는 것이 이해에 도움을 주지만 정확하지 않음
JS 엔진은 마법처럼 앞을 내다보고 선언문을 찾지 못하며 프로그램의 모든 스코프 경계와 선언문을 정확히 찾을 수 있는 유일한 방법은 코드를 파싱하는 것
파싱 없이 프로그램을 실행할 방법은 어디에도 없음
부정확한 비유를 연상시키는 호이스팅이라는 용어가 유용하지 않음
TC39 위원들이 여전히 호이스팅이라는 용어를 사용하고 있지만, 호이스팅을 소스 코드 재배열이라고 주장해서는 안 됨
호이스팅은 런타임에 일어나는 동작이 아닌 컴파일 타임에 일어나는 작업의 일부로 바라봐야 함
동일한 스코프에서 변수가 재선언(re-declaration)되는 경우는 없음
스코프 내에서 이미 선언된 변수를 중간 부분에서 다시 선언하낟고 해서 실제로 어떤 작업이 수행되지 않음
var greeting; function greeting() { console.log('안녕하세요!'); } // 의미 없는 작업 var greeting; typeof greeting; // 'function' var greeting = '안녕하세요!'; typeof greeting; // 문자열
var를 사용하여 식별자를 스코프에 등록한 첫 번째 줄의 greeting은 undefined로 자동 초기화
greeting 함수 선언에서 greeting을 다시 스코프에 등록하지는 않지만 함수 호이스팅이 일어나고, 함수 호이스팅은 변수 호이스팅보다 우선순위가 높으므로 greeting을 함수 참조로 초기화
두 번째 var greeting은 이미 greeting이 식별자로 등록되어 있고 함수 호이스팅이 더 우선순위가 높기 때문에 아무런 작업도 수행하지 않음
마지막 var greeting = '안녕하세요!'; 에서는 greeting 변수에 '안녕하세요!' 라는 문자열을 할당하며 여기서 var는 아무 역할을 하지 않음
동일 스코프에서 let이나 const로 중복 선언을 하는 경우는 실행되지 않고 즉시 SyntaxError 발생
JS 환경에 따라 다르지만 '이미 선언되었다'고 오류 메세지 표시
let이나 const를 사용한 재선언은 명시적으로 허용되지 않음
같은 이름을 가진 변수를 둘 다 let으로 선언하는 경우에만 발생하는 것이 아니라, 두 선언 중 하나가 let이고 하나가 var인 경우에는 오류가 발생
var studentName = '보라'; let studentName = '지수';
let studentName = '보라'; var studentName = '지수';
두 경우 모두 두 번째 선언에서 SyntaxError qkftod
변수를 재선언하는 방법은 선언에 모두 var를 사용하는 방법이 유일
let과 var를 섞어서 재선언하면 안 되는 이유는, var를 사용한 재선언은 이전부터 지금까지 허용되며, 기술적인 문제가 있는 것은 아님 let도 기술적으로 재선언은 가능
TC39 위원을 비롯한 업계 관계자는 변수를 재선언하는 습관을 버그를 일으키는 나쁜 습관으로 여김
이 때문에 ES6에 let을 추가할 때 오류와 함께 재선언을 방지하기로 함
컴파일러가 스코프 매니저에 선언에 대한 정보를 요청할 때, 이미 해당 식별자가 선언되어 있고 그 중 하나 이상이 let으로 선언된 경우 오류가 발생
이 오류는 개발자에게 '무심코 선언을 반복하지 마세요'라는 경고
const 키워드는 let보다 제약 조건이 더 많음
const 역시 let과 마찬가지로 동일한 스코프 내에 동일한 식별자 사용 불가
let은 문체상의 이유로 재선언을 허용하지 않지만 const가 재선언을 허용하지 않는 데에는 기술적 이유가 있음
const 키워드를 사용하려면 기본적으로 변수를 초기화해야 하므로 할당을 생략하면 SyntaxError 발생하며, const로 변수를 선언하면 재할당이 불가능한 변수가 생성
const studentName = '보라'; console.log(studentName); // 보라 studentName = '지수'; // TypeError
studentName에 재할당할 때 발생하는 오류는 SyntaxError가 아닌 TypeError
SyntaxError는 프로그램이 실행 전 발생하는 오류로 프로그램을 실행조차 하지 못하게 함
TypeError는 프로그램 실행 중에 발생하는 오류로 프로그램이 이미 실행중인 상태에서 발생
const로 선언된 변수를 다시 선언하는 것은 해당 변수를 재할당 하는 것과 같음
const 선언은 재할당 할 수 없으며 항상 할당이 필요하며, 이러한 기술적인 이유로 const를 사용한 재선언은 허용되지 않음
반복문에서는 새로운 반복이 시작될 때마다 자체적인 새 스코프가 생성
스코프 규칙(let으로 생성한 변수 재선언 포함)은 각 스코프 인스턴스마다 적용되며, 실행 중에 각 스코프가 시작될 때마다 모든 것이 초기화
반복문 안의 var 선언은 블록 스코프 선언으로 취급되지 않고 전역 스코프에 연결
let $$i = 0; for(; $$i < 3; $$i++) { let i = $$i; let value = i * 10; console.log(`${ i }: ${ value }`); } // 0: 0 // 1: 10 // 2: 20
for 반복문에서 변수 i와 value는 각 스코프 인스턴스마다 정확히 한 번만 선언되며 재선언이 일어나지 않음
for...in과 for...of 반복문에서 마찬가지로 선언한 변수는 반복문 본문 스코프에 속한 변수로 취급되므로 반복이 일어날 때마다 인스턴스 스코프가 생성되고 해당 스코프 내에서 처리되며 재선언은 일어나지 않음
const는 반복문이 이터레이션 될 때마다 정확히 한 번 실행되기 때문에 재선언 문제에서는 안전하나 for 반복문에서는 문제가 발생
for...in, for...of 반복문은 const와 함께 사용해도 무방하나, 일반적인 for 반복문에서는 const를 사용하는 것이 안전하지 않음
const $$i = 0; for(; $$i < 3; $$i++) { const i = $$i; // ... }
'$$i'를 const로 선언했지만 반복문 내에서 증가시킨다는 데 문제가 있음
const는 재할당이 허용되지 않으며 예시에서는 재선언이 아닌 재할당이 일어나서 오류 발생
따라서 일반 for문에서 const를 사용하면 재할당이 필요하기 때문에 사용하면 안 됨
var로 선언한 변수는 해당 스코프의 맨 위로 올라가는데, undefined로 자동 초기화 되므로 스코프 전체에서 사용 가능
let과 const로 선언한 변수는 작동 방식이 조금 다름
console.log(studentName); // ReferenceError let studentName = '지수';
해당 예제를 실행하면 ReferenceError 혹은 'Cannot access studentName before initialization' 라는 오류 메세지가 표시 됨
studentName = '지수'; // 초기화 시도 // ReferenceError console.log(studentName); let studentName;
첫 번째 줄에서 초기화를 시도했지만 여전히 ReferenceError 발생
따라서 let/const로 변수를 선언할 때는 선언문에 할당을 덧붙이는 것만이 유일한 방법
let studentName = '지수'; console.log(studentName); // 지수
let studentName; // 또는 let studentName = undefined; // ... studentName = '지수'; console.log(studentName); // 지수
var로 선언한 변수는 스코프 맨 위에서 자동으로 undefined로 초기화되기 때문에 var studentName; 과 var studentName = undefined; 가 동일하지 않지만
let으로 선언한 변수는 초기화되지 않으며 초깃값이 없는 상태로 선언되기 때문에 두 개의 예시가 동일하게 작동
컴파일러는 프로그램 중간, studentName이 선언된 지점에서 해당 선언을 자동으로 초기화하는 명령을 내리며, 초기화가 발생하기 전까지는 변수를 사용할 수 없음
const로 선언한 변수도 예외 없이 적용
스코프에 진입한 후 변수 자동 초기화가 일어나기까지의 시간을 지칭하기 위해 TC39 위원회는 TDZ(Temporal Dead Zone)라는 새로운 용어를 만듦
TDZ란 변수는 존재하지만 초기화되지 않아 어떤 방식으로도 해당 변수에 접근할 수 없는 시간대를 의미
변수의 초기화는 원래 선언 지점에 남긴 명령을 실행할 때만 발생하며 초기화가 이루어진 이후에 TDZ는 종료되고, 스코프 내에서 변수를 자유롭게 사용 가능
var에도 TDZ가 존재하긴 하지만 길이가 0이므로 프로그램 내에서 var로 선언한 변수는 TDZ를 관찰할 수 없으며, let과 const에서만 TDZ 관찰 가능
askQuestion(); // ReferenceError let studentName = '지수'; function askQuestion() { console.log(`${ studentName } 님, 질문해도 될까요?`); }
TDZ는 위치(Zone)라기보다는 시간대라고 해석하는 것이 좋음
위치 상으로는 studentName을 참조하는 console.log()가 let studentName 선언 뒤에 오지만 타이밍상으로는 studentName이 아직 TDZ에 있는 동안 let 문을 만나기 전에 askQuestion() 함수가 호출되었기 때문에 오류가 발생
변수 선언과 초기화에 대한 흔한 오해 중 하나는 TDZ 때문에 const와 let은 호이스팅되지 않는다는 것이지만, let과 const도 호이스팅 됨
let/const 선언은 var처럼 스코프 시작 부분에서 자동으로 초기화되지 않는다는 차이가 있음
변수가 스코프의 맨 위에서 자동으로 등록되는 것과 스코프의 맨 위에서 변수가 자동으로 초기화되는 것은 별개의 작업이며, 이 둘을 '호이스팅' 이라는 단일 용어로 묶어서는 안 됨
let/const 선언은 선언을 스코프의 맨 위에 올려놓지만, var와 달리 변수의 자동 초기화가 원래 선언이 나타난 코드가 처리될 때까지 연기되기 때문에 TDZ 오류가 발생
임시적으로 변수 초기화가 연기되는 시간은 그 길이에 상관 없이 TDZ가 존재하는 기간 동안
TDZ 오류를 피하기 위해서는 let과 const 선언은 스코프 맨 위에 두는 것이 좋음
이런 식으로 하면 TDZ 길이를 0 또는 0에 가깝게 줄일 수 있기 때문에 문제가 발생하지 않음
호이스팅은 일반적으로 JS 엔진에서 일어나는 명시적인 동작이라고 알려져 있지만, 실제로는 컴파일 중에 JS가 변수 선언을 처리하는 다양한 방식을 설명하기 위한 비유에 가까움
비유이긴 하지만 호이스팅은 변수의 생명주기(생성, 사용 가능, 소멸)를 이해하는데 도움을 줌
변수 선언과 재선언은 런타임에 관련된 작업이라고 생각하면 혼란을 야기하기 쉬움
컴파일 타임 작업이라고 사고를 전환하면 단점이 사라지고 보이지 않던 것들이 보일 수 있음
TDZ 오류는 개발자들을 당황스럽게 하지만 스코프 맨 위에 let/const를 선언하면 비교적 간단하게 TDZ 오류를 피할 수 있음