Jaewonism - POST : 자바스크립트 변수 생명주기 - 왜 let 은 호이스팅되지 않을까?, Github, Front-end, Back-end, Web Developer, 웹 개발자, 프론트 엔드, 개발자, 백 엔드, Node JS, Express JS, Ruby On Rails

JOIN
LOGIN
background-image
Java Script SEP 16. 2016
자바스크립트 변수 생명주기 - 왜 let 은 호이스팅되지 않을까?

본 글은 Dmitri Pavlutin 의 허락을 받고, JavaScript variables lifecycle: why let is not hoisted 를 번역한 것입니다.
오역 또는 정확하지 않은 표현, 빠진 부분이 있을 수도 있다는 점 유의하셔서 보시기 바랍니다.


호이스팅은 변수 표현식 var 와 함수 선언 functino func() { … } 이 해당 스코프의 시작 부분에 정의되는 과정이다.
let 선언(constclass 또한)이 ES2015 에 의해 소개되었을 때, 많은 개발자들은 ‘어떻게 변수에 접근할지’ 기술하기 위해 호이스팅 정의를 사용하고 있었다.
하지만 이 문제에 대한 검색 이후, 호이스팅은 let 변수의 초기화와 사용을 기술하기에는 잘못된 방법이었다.


ES2015 는 let 에 대해 기존과는 다르고 개선된 메카니즘을 제공한다.
let 은 엄격한 변수 선언 방식을 요구함으로써 코드의 질이 더 좋아진다.
다음 몇 가지 과정을 통해 더 자세히 알아 보겠다.


1. 에러가 발생하기 쉬운 var 호이스팅

가끔 변수 var varname 과 함수 function funName() { … } 를 스코프 안의 아무 공간에 선언하는 예제들이 있다.


1
2
3
4
5
6
7
8
9
10
11
// var hoisting
num; // => undefined
var num;
num = 10;
num; // => 10
// function hoisting
getPi; // => function getPi() {...}
getPi(); // => 3.14
function getPi() {
return 3.14;
}


변수 numvar num 으로 정의되기 전에 접근됐고, numundefined 로 평가되었다.
함수 function getPi() { … } 는 파일의 마지막 부분에서 정의되었다. 하지만, 함수는 스코프의 최상단에 호이스팅 되었기 때문에 정의되기 전에 호출이 가능하다.
이것은 전형적인 호이스팅 이다.


변수나 함수를 먼저 사용하고 나중에 정의하는 것은 혼란을 야기할 수 있다는 것을 알 수 있다.
큰 파일을 보던 중, 선언되지 않은 변수를 보게 된다고 가정하면, 어떻게 쓰거나 어디에 정의해야겠는가?
물론, 숙련된 개발자는 이런 식으로 코드를 짜지 않는다. 하지만, 수천개의 자바스크립트 깃허브 저장소에 이런 코드들이 있을 확률이 꽤 된다.


위 예제 코드에서 보았듯이, 코드에서 선언 흐름을 이해하는 것은 어렵다.


먼저, 확인되지 않은 용어를 기술하거나 선언하고, 나중에 구문을 만들어야 한다. let 은 이런식으로 변수에 접근할 수 있도록 한다.


2. (내부적인) 변수의 생명주기

엔진이 변수와 함께 작동할 때, 그들의 생명주기는 다음 단계들로 되어있다.


  1. 선언 단계에서는 스코프에 변수를 등록한다.

  2. 초기화 단계에서는 스코프에 있는 변수를 메모리에 할당하고 바인딩한다. 이 단계에서 변수는 자동으로 undefined 로 초기화된다.

  3. 할당 단계에서는 초기화된 변수에 값을 할당한다.



여기서 알아야 할 것은,
선언 단계는 실제로 코드 상에서 변수가 선언되는 과정과 다르다는 것이다.


3. var 변수 생명주기

다음은, 엔진이 var 변수를 어떻게 다루는지 알아본다.



자바스크립트가 내부에 변수 표현식을 가지는 함수의 스코프를 만났다고 가정한다.
그 변수는 다른 코드들이 실행되기 이전인 스코프의 시작지점에서 선언 단계와 초기화 단계를 지난다. (step 1)
var 변수 표현식은 선언 단계와 초기화 단계에 영향을 주지 않는 함수 스코프 안에 위치한다.


선언과 초기화 단계가 지나고, 할당 단계 전에 변수는 undefined 값을 가지며, 사용될 수 있다.


할당 단계인 variable = ‘value’ 에서 변수는 초기값을 받는다. (step 2)


정확히 말하면, 호이스팅은 ‘변수는 함수 스코프의 시작지점에서 선언되고 초기화되어진다.’는 개념이며, 선언과 초기화 단계의 갭이 없다.


다음 코드는 내부에 var 표현식이 있는 함수 스코프를 만드는 것이다.


1
2
3
4
5
6
7
8
function multiplyByTen(number) {
console.log(ten); // => undefined
var ten;
ten = 10;
console.log(ten); // => 10
return number * ten;
}
multiplyByTen(4); // => 40


자바스크립트가 multipleByTen(4) 를 실행하고 함수 스코프에 진입하면, 변수 ten 은 첫 번째 코드 실행 이전에 선언 단계와 초기화 단계를 거친다.
따라서 console.log(ten)undefined 를 출력하게 되는 것이다.


표현식 ten = 10 은 변수 ten 에 초기값을 할당한다.
할당 다음, console.log(ten) 은 10 이란 값을 올바르게 출력한다.


4. 함수 선언 생명주기

함수 선언 표현식인 function funName() { … } 인 경우에는 훨씬 간단하다.



선언, 초기화, 할당 단계가 함수 스코프가 둘러싸인 시작지점에서 한 번에 이뤄진다.
funName() 은 선언 표현식이 어디에 있던 스코프 내 어디서든(심지어 끝 부분이라도) 호출될 수 있다.


다음 예제는 함수 호이스팅을 설명한다.


1
2
3
4
5
6
7
function sumArray(array) {
return array.reduce(sum);
function sum(a, b) {
return a + b;
}
}
sumArray([5, 10, 8]); // 23


자바스크립트가 sumArray([5, 10, 8]) 을 실행할 때, sumArray 함수 스코프에 진입한다.
스코프 내부에서, 어떤 표현식이라도 실행하기 바로 직전에, 함수 sum선언, 초기화, 할당 세 단계를 모두 거친다.
이로써 array.reduce(sum)sum 함수가 선언되기 전이라도 사용할 수 있는 것이다.


5. let 변수 생명주기

let 변수는 var 와는 다른 과정을 거친다.
주된 차이는 선언 단계와 초기화 단계가 분리된다는 것이다.



인터프리터가 let 변수 표현식을 포함하는 블록 스코프에 진입하면, 곧바로 그 변수는 스코프에 자신의 이름을 등록하는 선언 단계를 지난다.(step 1)
그리고, 인터프리터는 블록을 한줄 한줄 해석한다.


이 단계에서 변수에 접근하려고 한다면, 자바스크립트는 ReferenceError: variable is not defined. 에러를 발생시킬 것이다. 이 단계에서 변수가 초기화되지 않았기 때문이다.
이 때, 변수는 일시적인 사각지대(temporal dead zone)에 있다.


인터프리터가 let variable 표현식에 진입하면, 초기화 단계를 통과하게 된다.(step 2)
이제, 변수 상태는 초기화되었고, 변수에 접근하게 되면 undefined 로 평가되었으며, 이 변수는 일시적 사각지대에서 벗어나게 되었다.


할당 표현식 variable = ‘value’ 다음에는, 할당 단계를 통과한다.(step 3)


만약, 자바스크립트가 let variable = ‘value’ 를 해석하게 되면, 초기화와 할당이 한 표현식 안에서 이루어지게 된다.


아래는 let 변수 number 가 블록 스코프 안에서 만들어지는 예제이다.


1
2
3
4
5
6
7
8
let condition = true;
if (condition) {
// console.log(number); // => Throws ReferenceError
let number;
console.log(number); // => undefined
number = 5;
console.log(number); // => 5
}


자바스크립트가 if (condition) { … } 블록 스코프 안에 진입하면, number 는 즉시 선언 단계를 지나게 된다.
number 는 초기화되지 않았고, 일시적인 사각지대에 있기 때문에 변수에 접근하려고 할 때, RegerenceError: number is not defined. 가 발생하게 된다.
let number 표현식이 자니고 초기화가 되며, 변수는 그제서야 접근 가능해진다. 하지만, 변수의 값은 undefined 이다.
할당 표현식 number = 5 는 할당 할당 단계에 접어들게 한다.


constclasslet 과 같은 생명주기를 가진다. 다른 점은 할당을 단 한번만 할 수 있다는 것이다.


5.1 왜 호이스팅은 let 생명주기에서는 유효하지 않는가

위에서 언급했듯이, 호이스팅은 스코프의 가장 위에서 일어나는 변수의 선언과 초기화이다.
let 생명주기는 선언과 초기화 단계를 분리하며, 이 분해는 let 에서의 호이스팅을 없앤다. 또, 두 단계의 사이에는 일시적인 사각지대가 생기며, 이 때에는 변수에 접근할 수 없다.


6. 결론

let 은 변수를 선언하는 데에 개선된 알고리즘과 블록 스코프를 사용한다.

선언 단계와 초기화 단계가 분리되었기 때문에, 호이스팅은 let (constclass 도 포함하여) 변수에 대해 유효하지 않다.

초기화 전에 변수는 일시적인 사각지대에 있으며, 이때 변수에 접근할 수 없다.


다음은 변수 선언을 좀더 매끄럽게 하기 위한 팁이다.

  • - 변수를 사용하기 전에 선언과 초기화를 한다.
  • - 가능하다면 변수를 숨기고, 최소한의 변수만 노출시켜라.