8/30/2023

Hoisting & TDZ

Hoisting

호이스팅은 기본적으로 일부 유형의 변수를 코드에서 실제로 선언되기 전에 접근할 수 있게(사용할 수 있도록) 만든다. 호이스팅은 표면상으로 변수가 코드의 맨 위로 이동되는 것처럼 보이지만 실행되기 전에 변수 선언을 위해 코드를 스캔하며 이는 실행 컨텍스트의 생성 단계에서 발생한다. 그런 다음 코드에서 찾은 각 변수에 대해 변수 환경 객체(variable environment object)에 새로운 속성이 생성된다. 이것이 호이스팅이 실제로 작동하는 방식이다. 모든 변수 유형에 대해 호이스팅이 동일하게 작동하는 것은 아니다.

function declaration

함수 선언식은 호이스팅 되며 변수 환경에서의 초깃값은 실제 함수로 설정된다. 이는 실제로 코드에서 선언되기 전에 함수 선언식을 사용할 수 있다는 것을 의미하는데 실제 코드 실행이 시작되기 전에 변수 환경 객체에 저장되기 때문이다. 함수 선언식은 엄격한(strict) 모드에서는 블록 스코프를 가지며 해제된(sloppy) 모드를 사용하며 함수 스코프를 가진다.

var

var로 선언된 변수도 호이스팅 되지만 함수와는 다르게 코드에서 선언되기 전에 var 변수에 접근하려고 하면 선언된 값을 얻지 못하고 undefined를 얻는다. 이 행동은 JavaScript에서 버그의 주요 원인 중 하나이기 때문에 현대 JavaScript에서 var를 거의 사용하지 않는 주요 이유 중 하나이다.

let & const

let과 const 변수는 호이스팅되지 않는 데 기술적으로는 실제로 호이스팅되지만 값이 초기화되지 않아서(uninitialized) 사용할 수 있는 값이 없어서 실제로는 호이스팅이 전혀 일어나지 않는 것처럼 보인다. 변수들은 시간적 사각지대(Temporal Dead Zone, TDZ)라는 변수가 선언된 위치와 스코프의 시작점 사이에 배치되어 변수에 접근할 수 없다. 따라서, 변수를 선언하기 전에 let 또는 const 변수를 사용하려고 하면 오류가 발생한다. 또한 let과 const는 블록 스코프를 가지기 때문에 생성된 블록 내에서만 존재한다. 이러한 요소들 덕분에 let과 const가 JavaScript에 도입되었고 현대 JavaScript에서 var 대신에 let과 const를 사용한다.

function expression & arrow function

함수 표현식과 화살표 함수의 호이스팅 여부는 var, const 또는 let을 사용하여 생성되었느냐에 따라 달라지는데 이 함수들은 단순히 변수이기 때문이다. 따라서 사용된 변수 예약어의 호이스팅과 정확히 동일한 방식으로 작동한다. 함수 표현식이나 화살표 함수가 var로 생성된 경우 undefined로 호이스팅 되지만 let 또는 const로 생성된 경우 TDZ 때문에 코드에서 선언되기 전에 사용할 수 없다.

호이스팅은 많은 문제를 일으키는데 왜 탄생했을까? JavaScript의 창시자는 기본적으로 함수 선언식을 사용하기 전에 함수 선언식을 사용할 수 있도록 호이스팅을 구현했는데 상호 재귀(mutual recursion)와 같은 일부 프로그래밍 기법에 필수적이기 때문이다. 또한 일부 사람들은 이로 인해 코드가 훨씬 가독성이 좋아진다고 생각한다. var 선언에 대한 호이스팅이 작동하는 이유는 당시에 호이스팅을 구현할 수 있는 유일한 방법이었기 때문이다. var 변수의 호이스팅은 사실상 함수의 호이스팅의 부산물에 불과하다. 현재는 이 문제를 회피하기 위해 let과 const를 사용하도록 한다.

TDZ

기본적으로 모든 let과 const 변수는 스코프의 시작부터 정의된 라인까지 자체 TDZ를 가진다. 해당 변수는 TDZ 이후에 안전하게 사용할 수 있다. ES6에서 TDZ가 도입된 주요 이유는 앞서 설명한 동작이 오류를 피하고 발견하기 쉽게 만든다는 점이다. 실제로 선언되기 전에 undefined로 설정된 변수를 사용하는 것은 심각한 버그를 발생시킬 수 있으며 버그를 찾기가 어려울 수 있다. 선언하기 전에 변수에 접근하는 것은 바람직하지 않으며 방지해야 하는데 방지하는 가장 좋은 방법은 해당 작업을 시도하려 할 때 오류를 받는 것이다. 두 번째로 이유는 TDZ가 const 변수가 실제로 작동하는 방식을 지원하기 위해서다. const 변수를 다시 할당할 수 없기 때문에 먼저 undefined로 설정한 다음 실제 값을 나중에 할당하는 것이 불가능하다.


Exercise

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
"use strict";
 
// 변수.
console.log(me); // undefined
 
// 변수가 TDZ에 존재한다.
// ReferenceError: Cannot access before initialization
console.log(job);
console.log(year);
 
var me = "박선심";
let job = "경찰";
const year = 1966;
 
// 함수
console.log(mulDecl(510)); // 50
 
// 함수가 TDZ에 존재한다.
// ReferenceError: Cannot access before initialization
console.log(mulExpr(510));
console.log(mulArrow(510));
 
// var를 사용하면 호이스팅을 통해서 undefined.
// TypeError: mulArrowVar is not a function
// 즉, undefined(5, 10)
console.log(mulExprVar(510));
console.log(mulExprVar(510));
 
function mulDecl(n1, n2) {
  return n1 * n2;
}
 
const mulExpr = function (n1, n2) {
  return n1 * n2;
};
 
const mulArrow = (n1, n2) => {
  return n1 * n2;
};
 
var mulExprVar = function (n1, n2) {
  return n1 * n2;
};
 
var mulArrowVar = (n1, n2) => {
  return n1 * n2;
};
 
// 호이스팅의 위험성.
// numPosts === undefined
// 함수가 실행된다.
if (!numPosts) deleteAllPosts();
 
var numPosts = 20;
 
function deleteAllPosts() {
  console.log("All posts deleted");
}
 
// var는 let, const와 달리 전역 window 객체에 속성을 생성한다.
console.log(me === window.me);
console.log(numPosts === window.numPosts);
 
cs

update: 2023-09-04

댓글 없음:

댓글 쓰기