|
| 1 | +# Chapter 15 - let, const 키워드와 블록 레벨 스코프 |
| 2 | + |
| 3 | +ES5까지 변수를 선언할 수 있는 유일한 방법은 `var` 키워드를 사용하는 것입니다. |
| 4 | +`var` 키워드는 다음과 같은 특징을 가지고 있습니다. |
| 5 | + |
| 6 | +<br><br> |
| 7 | + |
| 8 | +#### var - 변수 중복 선언 허용 |
| 9 | +`var` 키워드로 선언한 변수는 중복 선언이 가능합니다. |
| 10 | + |
| 11 | +``` js |
| 12 | +var x = 1; |
| 13 | +var y = 1; |
| 14 | + |
| 15 | +// var 키워드를 사용하면 같은 스코프 내에서 중복 선언을 허용한다. |
| 16 | +var x = 100; |
| 17 | + |
| 18 | +// 초기화 문이 없는 변수 선언문은 무시된다. |
| 19 | +var y; |
| 20 | + |
| 21 | +console.log(x); // 100 |
| 22 | +console.log(y); // 1 |
| 23 | +``` |
| 24 | + |
| 25 | +만약 동일한 이름의 변수가 이미 선언되어 있는 것을 모르고 변수를 중복 선언하며 값을 할당했을 경우, 의도치 않게 먼저 선언된 변수 값이 변경되는 부작용이 발생합니다. |
| 26 | + |
| 27 | + |
| 28 | +<br><br> |
| 29 | + |
| 30 | +#### var - 함수 레벨 스코프 |
| 31 | +`var` 키워드로 선언한 변수는 오로지 함수의 코드 블록만을 지역 스코프로 인정하게 됩니다. |
| 32 | +따라서 함수 외부에서 `var` 키워드로 선언한 변수는 블록 내에서 선언해도 모두 전역 변수가 됩니다. |
| 33 | + |
| 34 | +``` js |
| 35 | +var x = 1; |
| 36 | + |
| 37 | +if (true) { |
| 38 | + // x는 전역 변수이다. 이미 선언된 전역 변수 x가 있으므로 x는 중복 선언된다. |
| 39 | + // 의도치 않게 변수값이 변경되는 부작용이 발생한다. |
| 40 | + var x = 100; |
| 41 | +} |
| 42 | + |
| 43 | +console.log(x); // 100 |
| 44 | +``` |
| 45 | + |
| 46 | +`for` 문의 변수 선언문에서 `var` 키워드로 선언한 변수도 전역 변수가 됩니다. |
| 47 | + |
| 48 | +<br> |
| 49 | + |
| 50 | +``` js |
| 51 | +var i = 10; |
| 52 | + |
| 53 | +// for 문의 변수 선언문에서 var 키워드로 선언한 변수도 전역 변수가 된다. |
| 54 | +for (var i = 0; i < 5; i++) { |
| 55 | + console.log(i); // 0 1 2 3 4 |
| 56 | +} |
| 57 | + |
| 58 | +// 의도치 않게 i 변수의 값이 변경된다. |
| 59 | +console.log(i); // 5 |
| 60 | +``` |
| 61 | + |
| 62 | +<font color='orange'>함수 레벨 스코프는 전역 변수를 남발할 가능성을 높입니다. |
| 63 | +이로 인해 의도치 않게 전역 변수를 중복 선언하는 경우가 발생합니다.</font> |
| 64 | + |
| 65 | + |
| 66 | +<br><br> |
| 67 | + |
| 68 | +#### var - 변수 호이스팅 |
| 69 | +`var` 키워드로 변수를 선언하면 변수 호이스팅에 의해 변수 선언문이 스코프의 선두로 끌어올려진 것처럼 동작합니다. |
| 70 | +> `var` 키워드로 선언한 변수는 변수 선언문 이전에 참조할 수 있다. |
| 71 | +
|
| 72 | +``` js |
| 73 | +console.log(foo); // undefined |
| 74 | + |
| 75 | +// 변수에 값을 할당 |
| 76 | +foo = 123; |
| 77 | +console.log(foo); // 123 |
| 78 | + |
| 79 | +// 변수 선언문은 런타임 이전에 자바스크립트 엔진에 의해 암묵적으로 실행된다. |
| 80 | +var foo; |
| 81 | +``` |
| 82 | + |
| 83 | + |
| 84 | +<br><br><hr> |
| 85 | + |
| 86 | +### let 키워드 |
| 87 | +`var` 변수의 단점을 보완하기 위해 ES6에서는 새로운 변수 키워드인 `let`과 `const`를 도입했습니다. |
| 88 | + |
| 89 | +<br> |
| 90 | + |
| 91 | +#### let - 변수 중복 선언 금지 |
| 92 | +`var` 키워드로 이름이 동일한 변수를 중복 선언하면 아무런 에러가 발생하지 않습니다. |
| 93 | +하지만 `let` 키워드로 이름이 같은 변수를 중복 선언하면 문법 에러(`SyntaxError`)가 발생합니다. |
| 94 | + |
| 95 | +``` js |
| 96 | +// var 키워드는 중복 선언을 허용함 |
| 97 | +var foo = 123; |
| 98 | +var foo = 456; |
| 99 | + |
| 100 | +// let 키워드는 중복 선언을 허용하지 않음 |
| 101 | +let bar = 1234; |
| 102 | +let bar = 5678; // Uncaught SyntaxError: Identifier 'bar' has already been declared |
| 103 | +``` |
| 104 | + |
| 105 | +<br> |
| 106 | + |
| 107 | +#### let - 블록 레벨 스코프 |
| 108 | +`var` 키워드로 선언한 변수는 오로지 함수의 코드 블록만을 지역 스코프로 인정하는 함수 레벨 스코프를 따르지만, |
| 109 | +`let` 키워드로 선언한 변수는 모든 코드 블록(함수, if, for, while, try/catch 등)을 지역 스코프로 인정하는 블록 레벨 스코프를 따릅니다. |
| 110 | + |
| 111 | +``` js |
| 112 | +let foo = 1; // 전역 변수 |
| 113 | +{ |
| 114 | + let foo = 2; // 지역 변수 |
| 115 | + let bar = 3; // 지역 변수 |
| 116 | +} |
| 117 | + |
| 118 | +console.log(foo); // 1 |
| 119 | +console.log(bar); // ReferenceError: bar is not defined |
| 120 | +``` |
| 121 | + |
| 122 | +<br> |
| 123 | + |
| 124 | +#### let - 변수 호이스팅 |
| 125 | +`var` 키워드로 선언한 변수와 달리 `let` 키워드로 선언한 변수는 변수 호이스팅이 발생하지 않는 것처럼 동작합니다. |
| 126 | + |
| 127 | +``` js |
| 128 | +console.log(foo); // Uncaught ReferenceError: foo is not defined |
| 129 | +let foo; |
| 130 | +``` |
| 131 | + |
| 132 | +`let` 키워드로 선언한 변수는 "선언 단계"와 "초기화 단계"가 분리되어 진행됩니다. |
| 133 | +> 즉, 런타임 이전에 자바스크립트 엔진에 의해 암묵적으로 선언 단계가 먼저 실행되지만 초기화 단계는 변수 선언문에 도달했을 때 실행된다. |
| 134 | +
|
| 135 | +`let` 키워드로 선언한 변수는 스코프의 시작 지점부터 초기화 시작 지점(변수 선언문)까지 변수를 참조할 수 없습니다. |
| 136 | +이처럼 스코프의 시작 지점부터 초기화 시작 지점까지 변수를 참조할 수 없는 구간을 일시적 사각지대(Temporal Dead Zone; TDZ)라고 부릅니다. |
| 137 | + |
| 138 | +<img width="350" alt="image" src="https://github.com/user-attachments/assets/135c7a43-7a61-4518-9461-0251b894e1a5"> |
| 139 | + |
| 140 | +<br> |
| 141 | + |
| 142 | +ES6에서 도입된 `let`, `const`, `class`를 사용한 선언문은 호이스팅이 발생하지 않는 것처럼 동작한다는 것을 기억합시다. |
| 143 | + |
| 144 | + |
| 145 | +<br><br> |
| 146 | + |
| 147 | +#### 전역 객체와 let |
| 148 | +`var` 키워드로 선언한 전역 변수와 전역 함수, 그리고 선언하지 않은 변수에 대한 값을 할당한 암묵적 전역은 전역 객체 `window`의 프로퍼티가 됩니다. |
| 149 | + |
| 150 | +그리고 전역 객체의 프로퍼티를 참조할 때 `window`를 생략할 수 있습니다. |
| 151 | + |
| 152 | +``` js |
| 153 | +// 전역 변수 |
| 154 | +var x = 1; |
| 155 | + |
| 156 | +// 암묵적 전역 |
| 157 | +y = 2; |
| 158 | + |
| 159 | +// 전역 함수 |
| 160 | +function foo() {} |
| 161 | + |
| 162 | +// var 키워드로 선언한 전역 변수는 전역 객체 window의 프로퍼티이다. |
| 163 | +console.log(window.x); // 1 |
| 164 | +console.log(x); // 1 |
| 165 | + |
| 166 | +// 암묵적 전역은 전역 객체 window의 프로퍼티이다. |
| 167 | +console.log(window.y); // 2 |
| 168 | +console.log(y); // 2 |
| 169 | + |
| 170 | +// 함수 선언문으로 정의한 전역 함수는 전역 객체 window의 프로퍼티이다. |
| 171 | +console.log(window.foo); // foo() {} |
| 172 | +console.log(foo); // foo() {} |
| 173 | +``` |
| 174 | + |
| 175 | +<br> |
| 176 | + |
| 177 | +단, `let`, `const` 키워드로 선언한 전역 변수는 전역 객체의 프로퍼티가 아니다. |
| 178 | +> `window.foo`와 같이 접근할 수 없다. |
| 179 | +
|
| 180 | +`let` 전역 변수는 보이지 않는 개념적인 블록(전역 렉시컬 환경의 선언적 환경 레코드) 내에 존재하기 때문입니다. |
| 181 | + |
| 182 | +``` js |
| 183 | +let x = 1; |
| 184 | + |
| 185 | +// let, const 키워드로 선언한 전역 변수는 전역 객체 window의 프로퍼티가 아니다. |
| 186 | +console.log(window.x); // undefined |
| 187 | +console.log(x); // 1 |
| 188 | +``` |
| 189 | + |
| 190 | + |
| 191 | +<br><br><hr> |
| 192 | + |
| 193 | +### const 키워드 |
| 194 | +`const` 키워드는 상수(constant)를 선언하기 위해 사용합니다. |
| 195 | +하지만 반드시 상수만을 위해 사용하지는 않는다. |
| 196 | + |
| 197 | +`const` 키워드의 특징은 `let` 키워드와 대부분 동일하므로 |
| 198 | +`let` 키워드와 다른 점을 중점으로 살펴보도록 합시다. |
| 199 | + |
| 200 | +<br> |
| 201 | + |
| 202 | +#### 선언과 초기화 |
| 203 | +<font color='orange'>const 키워드로 선언한 변수는 반드시 선언과 동시에 초기화해야 합니다. </font> |
| 204 | + |
| 205 | +``` js |
| 206 | +const foo = 1; |
| 207 | +``` |
| 208 | +그렇지 않으면 문법 오류(SyntazError)가 발생한다. |
| 209 | + |
| 210 | +`const` 키워드로 선언한 변수는 `let` 키워드로 선언한 변수와 마찬가지로 블록 레벨 스코프를 가지며, 변수 호이스팅이 발생하지 않는 것 처럼 동작합니다. |
| 211 | + |
| 212 | + |
| 213 | +``` js |
| 214 | +{ |
| 215 | + // 변수 호이스팅이 발생하지 않는 것 처럼 동작한다. |
| 216 | + console.log(foo); // ReferenceError: Cannot access 'foo' before initialization |
| 217 | + const foo = 1; |
| 218 | + console.log(foo); // 1 |
| 219 | +} |
| 220 | + |
| 221 | +// 블록 레벨 스코프를 가진다. |
| 222 | +console.log(foo); // ReferenceError: foo is not defined |
| 223 | +``` |
| 224 | + |
| 225 | +<br> |
| 226 | + |
| 227 | +#### 재할당 금지 |
| 228 | +`var` 또는 `let` 키워드로 선언한 변수는 재할당이 자유로우나 |
| 229 | +<font color='orange'>const 키워드로 선언한 변수는 재할당이 금지됩니다. </font> |
| 230 | + |
| 231 | +``` js |
| 232 | +const foo = 1; |
| 233 | +foo = 2; // TypeError: Assignment to constant variable |
| 234 | +``` |
| 235 | + |
| 236 | + |
| 237 | +<br> |
| 238 | + |
| 239 | +#### 상수 |
| 240 | +`const` 키워드로 선언한 변수에 원시 값을 할당한 경우 변수 값을 변경할 수 없습니다. |
| 241 | +**상수는 재할당이 금지된 변수를 의미합니다.** |
| 242 | + |
| 243 | +<font color='orange'>상수는 상태 유지와 가독성, 유지보수의 편의를 위해 적극적으로 사용되어야 합니다. </font> |
| 244 | + |
| 245 | +``` js |
| 246 | +// 세전 가격 |
| 247 | +let preTaxPrice = 100; |
| 248 | + |
| 249 | +// 세후 가격 |
| 250 | +// 0.1의 의미를 모르기 때문에 가독성에 좋지 않다 |
| 251 | +let afterTaxPrice = preTaxPrice + (preTaxPrice + 0.1) |
| 252 | +console.log(afterTaxPrice); // 110 |
| 253 | +``` |
| 254 | + |
| 255 | +여기서 0.1은 세율을 의미하는 값으로 쉽게 바뀌지 않는 값이며, 프로그램 전체에서 고정적으로 사용되는 값입니다. 이런 경우에 상수로 정의 시, 값의 의미를 쉽게 파악살 수 있고 변경될 수 없는 고정값으로 사용할 수 있습니다. |
| 256 | + |
| 257 | +<br> |
| 258 | + |
| 259 | +일반적으로 상수의 이름은 대문자로 선언해 상수임을 명확히 나타냅니다. |
| 260 | +여러 단어로 이뤄진 경우에는 언더스코어(_)로 구분해서 스네이크 케이스로 표현하는 것이 일반적입니다. |
| 261 | + |
| 262 | +``` js |
| 263 | +const TAX_RATE = 0.1; |
| 264 | + |
| 265 | +let preTaxPrice = 100; |
| 266 | +let afterTaxPrice = preTaxPrice + (preTaxPrice + TAX_RATE); |
| 267 | + |
| 268 | +console.log(afterTaxPrice); // 110 |
| 269 | +``` |
| 270 | + |
| 271 | + |
| 272 | +<br><br> |
| 273 | + |
| 274 | + |
| 275 | +#### const 키워드와 객체 |
| 276 | +`const` 키워드로 선언된 변수에 원시 값을 할당한 경우 값을 변경할 수 없습니다. |
| 277 | +하지만 <font color='orange'>const 키워드로 선언된 변수에 객체를 할당한 경우 값을 변경할 수 있습니다.</font> |
| 278 | + |
| 279 | +``` js |
| 280 | +const person = { |
| 281 | + name: 'Lee' |
| 282 | +}; |
| 283 | + |
| 284 | +// 객체는 변경 가능한 값입니다. 따라서 재할당 없이 변경이 가능합니다. |
| 285 | +person.name = 'Kim'; |
| 286 | +console.log(person); // {name: "Kim"} |
| 287 | +``` |
| 288 | + |
| 289 | +**`const` 키워드는 재할당을 금지할 뿐 "불변"을 의미하지 않는다는 것을 기억합시다.** |
| 290 | + |
| 291 | + |
| 292 | +<br><br><hr> |
| 293 | + |
| 294 | +### var, let, const |
| 295 | +`var`와 `let`, `const` 키워드는 다음 상황에서 사용하는 것을 권장합니다. |
| 296 | + |
| 297 | +- ES6을 사용한다면 `var` 키워드는 사용하지 않는다. |
| 298 | +- 재할당이 필요한 경우에 한정해 `let` 키워드를 사용합니다. 이때 변수의 스코프는 최대한 좁게 만든다. |
| 299 | +- 변경이 발생하지 않고 읽기 전용으로 사용되는 원시 값과 객체에는 `const` 키워드를 사용하는 것을 추천한다. |
| 300 | + |
| 301 | + |
0 commit comments