Skip to content

Commit 2601ab9

Browse files
authored
Update chapter_15.md
1 parent ff32174 commit 2601ab9

1 file changed

Lines changed: 164 additions & 8 deletions

File tree

book/공희재/chapter_15.md

Lines changed: 164 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,36 +15,192 @@ var y = 1;
1515
var x = 100; // 이렇게 초기화문이 있는 변수 선언문은 마치 var 키워드가 없는 것처럼 동작한다.
1616
var y; // 이렇게 초기화문이 없으면 그냥 무시됨.
1717

18-
console.log(x) // 100
19-
console.log(y) // 1
18+
console.log(x); // 100
19+
console.log(y); // 1
2020
```
2121

2222
### 1-2. 함수 레벨 스코프
23-
`var`로 선언한 변수는 오직 함수의 코드 블록만을 지역 스코프로 인정한다.<br>
24-
아래처럼 코드 블록 안에서 새로 선언한 변수가, 함수 밖에서 `var`로 선언한 변수와 이름이 같다면,<br>
23+
`var`로 선언한 변수는 오직 **함수의 코드 블록만을 지역 스코프로 인정**한다.<br>
24+
따라서 아래처럼 함수 외 코드 블록에서 `var`로 선언한 어떤 다른 전역 변수와 같은 이름으로 변수를 `var`로 선언한다면,<br>
2525
그 변수는 새로 선언한 변수가 아닌 **중복 선언한 전역 변수가 될 뿐**이다.
26+
27+
이렇듯 함수 레벨 스코프를 따르는 `var`를 사용하면 전역 변수를 남발할 수 있다는 문제가 있다.
2628
```javascript
27-
var x = 1;
29+
var i = 1;
2830

29-
if (true) {
30-
var x = 10; // 여기서 x는 이미 위에 선언한 전역 변수 x가 있으므로, 중복 선언한
31+
for (var i = 0; i < 5; i++) {
32+
// 여기서 i는 이미 위에 선언한 전역 변수 i와 이름이 같으므로, 새로운 변수가 아닌 중복 선언한 전역 변수가 된다.
33+
console.log(i); // 0 1 2 3 4
3134
}
3235

33-
console.log(x); // 10
36+
console.log(i); // 5
37+
```
38+
39+
### 1-3. 변수 호이스팅
40+
`var`로 선언한 변수는 **변수 호이스팅**에 의해 변수 선언문이 스코프의 선두로 끌어올려진 것처럼 동작한다.<br>
41+
즉, `var`로 선언한 변수는 **변수 선언문 이전에 참조**할 수 있으며, `undefined`로 참조된다.
42+
43+
이렇게 변수 선언문 이전에 변수를 에러 없이 참조하게 허용하는 `var`의 특징은,<br>
44+
프로그램의 흐름상 맞지 않을뿐더러 **가독성을 떨어뜨리고 오류를 발생시킬 여지**를 남긴다.
45+
```javascript
46+
// 이 시점, 이미 변수 호이스팅에 의해 foo 선언문이 실행돼 undefined로 초기화된다. (1. 선언, 2. 초기화 완료)
47+
console.log(foo); // undefined
48+
49+
foo = 123; // (3. 할당 완료)
50+
51+
console.log(foo); // 123
52+
53+
var foo;
3454
```
3555

3656
## 2. `let` 키워드
57+
앞에서 살펴본 `var`의 단점을 보완하기 위해<br>
58+
자바스크립트는 ES6부터 새로운 변수 선언 키워드 `let``const`를 도입했다.<br>
59+
`var``let`의 차이점을 살펴보자.
60+
3761
### 2-1. 변수 중복 선언을 금지함
62+
`var`과는 달리 `let`으로는 **같은 스코프 안에서 중복 선언을 허용하지 않는다**.<br>
63+
`let`으로 이름이 같은 변수를 중복으로 선언하면 `SyntaxError`가 발생한다.
64+
```javascript
65+
var foo = 123;
66+
var foo = 456; // 이 선언문은 자바스크립트 엔진에 의해 마치 var 키워드가 없는 것처럼 동작한다. 즉 에러가 발생하지 않는다.
67+
68+
let bar = 123;
69+
let bar = 456; // SyntaxError: Identifier 'bar' has already been declared
70+
```
71+
3872
### 2-2. 블록 레벨 스코프
73+
`var`과는 달리 `let`으로 선언한 변수는 함수 코드 블록뿐만이 아닌<br>
74+
**모든 코드 블록을 지역 스코프로 인정하는 block-level scope**(블록 레벨 스코프)를 따른다.
75+
76+
아래 예제에서, 코드 블록 밖에서 선언한 `foo` 변수와 안에서 선언한 `foo` 변수는 서로 다른 변수인 것을 볼 수 있다.<br>
77+
그리고 `bar` 변수도 블록 레벨 스코프를 갖는 지역 변수이므로 전역에서는 `bar` 변수를 참조할 수 없다.
78+
```javascript
79+
let foo = 1;
80+
81+
{
82+
let foo = 2;
83+
let bar = 3;
84+
}
85+
86+
console.log(foo); // 1
87+
console.log(bar); // ReferenceError: bar is not defined
88+
```
89+
아래 그림처럼, 여러 개의 코드 블록이 중첩하면 각 코드 블록마다의 스코프를 지닌다.<br>
90+
<img width="400px" src="https://github.com/user-attachments/assets/144c5a28-56ff-4e9f-ba5e-667cc4a355ac" />
91+
3992
### 2-3. 변수 호이스팅
93+
`var`과는 달리 `let`으로 선언한 변수는 **마치 변수 호이스팅이 발생하지 않는 것처럼 동작**한다.<br>
94+
```javascript
95+
console.log(foo); // ReferenceError: foo is not defined
96+
let foo;
97+
```
98+
99+
4장에서 배운 바로는 `var`로 선언한 변수는 자바스크립트 엔진에 의해<br>
100+
**런타임 이전에 암묵적으로 "선언 단계"와 "초기화 단계"를 한 번에 실행**한다.
101+
102+
그러나 `let`으로 선언한 변수는 이 두 단계를 분리한다.<br>
103+
즉, 런타임 이전에 암묵적으로 선언 단계를 먼저 실행하지만, **초기화 단계는 변수 선언문에 도달했을 때 실행**한다.
104+
105+
![image](https://github.com/user-attachments/assets/b4a430e9-0b5e-4b26-bb66-58bdcd7a2a9b)
106+
107+
만약 초기화 단계에 다다르기 전에 변수에 접근하려고 하면 `ReferenceError`가 발생한다.<br>
108+
왜냐하면 `let`으로 선언한 변수는 스코프의 시작 지점부터 변수 선언문까지 변수를 참조할 수 없기 때문이다.<br>
109+
이렇게 스코프의 시작 지점부터 초기화 시작 지점까지, 변수를 참조할 수 없는 구간을 **TDZ**(**Temporal Dead Zone**, 일시적 사각지대)라고 부른다.
110+
111+
그렇다면 `let`으로 선언한 변수는 호이스팅이 발생하지 않는 것일까? **그건 아니다**.<br>
112+
자바스크립트는 모든 선언(`var`, `let`, `const`, `function`, `function*`, `class` 등)을 호이스팅한다.<br>
113+
단, ES6에서 도입한 `let`, `const`, `class`를 사용한 선언문은 **TDZ로 인해 호이스팅이 발생하지 않는 것처럼 동작할 뿐**이다.
114+
115+
### 2-4. 전역 객체와 `let`
116+
`var`로 선언한 전역 변수, 전역 함수, 그리고 선언하지 않는 변수에 값을 할당한 Implicit Globals(암묵적 전역)은,<br>
117+
**전역 객체 `window`의 프로퍼티가 된다**. (`window`의 프로퍼티를 참조할 땐 `window`를 생략할 수 있다.)
118+
```javascript
119+
// 이 예제는 브라우저 환경에서 실행해야 함.
120+
121+
var x = 1; // 전역 변수
122+
y = 2; // implicit globals
123+
function foo() {} // 전역 함수
124+
125+
// var로 선언한 전역 변수는, 전역 객체 window의 프로퍼티가 된다.
126+
console.log(window.x); // 1
127+
console.log(x); // 1
128+
129+
// implicit globals는, 전역 객체 windows의 프로퍼티가 된다.
130+
console.log(window.y); // 2
131+
console.log(y); // 2
132+
133+
// 함수 선언문으로 정의한 전역 함수는, 전역 객체 windows의 프로퍼티가 된다.
134+
console.log(window.foo); // f foo() {}
135+
console.log(foo); // f foo() {}
136+
```
137+
138+
그러나 `let`으로 선언한 전역 변수는 **전역 객체 windows의 프로퍼티가 되지 않는다**.<br>
139+
```javascript
140+
// 이 예제는 브라우저 환경에서 실행해야 함.
141+
142+
let x = 1;
143+
144+
// let, const 키워드로 선언한 전역 변수는, 전역 객체 windows의 프로퍼티가 되지 않는다.
145+
console.log(window.x); // undefined
146+
console.log(x); // 1
147+
```
40148

41149
## 3. `const` 키워드
150+
`const` 키워드는 constant(상수) 선언을 위한 키워드지만, 사실 반드시 상수만을 위해 사용하지는 않는다.<br>
151+
`const`의 특징은 `let`의 특징과 대부분 비슷하므로 `let`과의 차이점을 위주로 `const`를 살펴보자.
152+
42153
### 3-1. 선언과 초기화
154+
`const`로 변수를 선언할 땐 **반드시 선언과 동시에 초기화를 해야 한다.** 그렇지 않으면 `SyntaxError`가 발생한다.
155+
```javascript
156+
const foo = 1;
157+
const foo; // SyntaxError: Missing initializer in const declaration
158+
```
159+
160+
그리고 `const`로 선언한 변수는 `let`과 마찬가지로 **block-level scope**(블록 레벨 스코프)를 가지며,<br>
161+
아래와 같이 마치 변수 호이스팅이 발생하지 않는 것처럼 동작한다.
162+
```javascript
163+
{
164+
console.log(foo); // ReferenceError: Cannot access 'foo' before initialization
165+
const foo = 1;
166+
console.log(foo); // 1
167+
}
168+
169+
console.log(foo); // ReferenceError: foo is not defined
170+
```
171+
43172
### 3-2. 재할당을 금지함
173+
`var``let`으로 선언한 변수와는 다르게, `const`로 선언한 변수는 재할당을 할 수 없다.
174+
```javascript
175+
const foo = 1;
176+
foo = 2; // TypeError: Assignment to constant variable.
177+
```
178+
44179
### 3-3. 상수
180+
`const`로 선언한 변수에 원시 값을 할당한 경우,<br>
181+
(원시 값은 immutable value이고, const 키워드는 재할당을 허용하지 않으므로) 할당값을 변경하는 방법은 없게 된다.<br>
182+
따라서 상태 유지와 가독성, 유지보수의 편의를 위해 상수를 적극적으로 사용해야 하며, `const`를 사용하면 된다.
183+
45184
### 3-4. `const` 키워드와 객체
185+
`const`로 선언한 변수에 원시 값을 할당한다면 값을 변경할 수 없지만, **객체를 할당한다면 값을 변경할 수 있다**.<br>
186+
```
187+
const person = {
188+
name: 'Lee'
189+
}
190+
person.name = 'Kim';
191+
console.log(person); // {name: "Kim"}
192+
```
193+
11장에서 살펴봤듯 `const`는 재할당을 금지할 뿐, "불변"을 의미하진 않는다.<br>
194+
다시 말해 **새로운 값을 재할당하는 것은 불가능하지만 프로퍼티의 동적인 생성, 삭제, 프로퍼티 값의 변경은 가능**하다.<br>
195+
이때 객체 내용이 변하더라도 변수에 할당한 **객체의 참조 값은 변하지 않는다**는 점을 주목하자.
46196

47197
## 4. `var` vs. `let` vs. `const`
198+
변수를 선언할 땐 기본적으로 `const`를 사용하고, 재할당이 필요한 때만 `let`을 제한적으로 사용하는 것이 바람직하다.<br>
199+
이 방식이 의도치 않은 재할당을 방지해 안전하기 때문이다. 우선 `const`로 선언하고, 재할당이 필요해지면 그때 `let`으로 바꿔도 늦지 않다.
48200

201+
`var`, `let` 그리고 `const` 권장 사용 방식을 정리하면 다음과 같다.
202+
1. ES6를 사용한다면 `var`는 아예 사용하지 않는다.
203+
2. 재할당이 필요한 때만 제한적으로 `let`을 사용한다. 이때 변수의 스코프는 최대한 좁게 만든다.
204+
3. 재할당이 필요 없는 상수(변경할 예정이 없거나, 읽기 전용으로 사용할 값들)로 쓰일 원시 값과 객체는 `const`를 사용한다.
49205

50206
끝.

0 commit comments

Comments
 (0)