Skip to content

Commit 5649515

Browse files
authored
Create chapter_24.md
1 parent a18a420 commit 5649515

File tree

1 file changed

+126
-0
lines changed

1 file changed

+126
-0
lines changed

book/공희재/chapter_24.md

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
# Chapter 24 - Closure(클로저)
2+
3+
## 1. Lexical Scope: 렉시컬 스코프
4+
자바스크립트 엔진은, 함수를 어디서 호출했는지가 아닌<br/>
5+
함수를 **어디에 정의했는지**에 따라 상위 스코프를 결정한다.<br/>
6+
이를 우리는 **Lexical Scope**(렉시컬 스코프, 정적 스코프)라고 한다.
7+
8+
상위 스코프는, 렉시컬 환경의 "외부 렉시컬 환경을 저장하는 참조 값"을 의미한다.<br/>
9+
상위 스코프 참조는 함수 정의가 평가되는 시점에, 함수가 정의된 환경(위치)에 의해 결정된다. 이것이 바로 렉시컬 스코프다.
10+
11+
## 2. 함수 객체의 내부 슬롯 `[[Environment]]`
12+
함수가 정의된 위치와 호출되는 위치는 다를 수 있다.<br/>
13+
따라서 **렉시컬 스코프가 제대로 기능하려면,** 함수는 자신이 어디서 호출되는지는 상관 없이<br/>
14+
자신이 정의된 위치, 즉 상위 스코프를 **기억**해야 한다.
15+
16+
이를 위해 함수는 **자신의 내부 슬롯 중 `[[Environment]]`라는 슬롯에 상위 스코프의 참조 값을 저장**한다.
17+
18+
## 3. Closure and The Lexical Environment: 클로저와 렉시컬 환경
19+
### Closure 소개
20+
다음 예제를 살펴 보자.
21+
```javascript
22+
const x = 1;
23+
24+
// (1)
25+
function outer() {
26+
const x = 10;
27+
const inner = function () { console.log(x); } // (2)
28+
return inner;
29+
}
30+
31+
// outer 함수를 호출하면 중첩 함수 inner를 반환한다.
32+
// 그리고 outer 함수의 실행 컨텍스트는 실행 컨텍스트 스택에서 pop해 제거된다.
33+
const innerFunc = outer(); // (3)
34+
innerFunc(); // (4) 10
35+
```
36+
37+
`(3)`에서 `outer` 함수는 `inner`를 반환하고 생명 주기를 마감한다.<br/>
38+
따라서 `outer` 함수 내부의 `x` 지역 변수는 마치 접근할 수 없는, 더는 유효하지 않은 변수처럼 보인다.
39+
40+
**그러나!!!** `(4)`를 보면 지역변수 `x`가 다시 부활이라도 한 것처럼 동작하고 있음을 알 수 있다.<br/>
41+
이처럼 외부 함수보다 **중첩 함수가 더 오래 살아있는 경우,** 그 중첩 함수는 외부 함수가 이미 죽었을지라도 **그 외부 함수의 변수를 참조**할 수 있다.<br/>
42+
**이러한 중첩 함수를 우리는 Closure라고 부른다.**
43+
44+
### Closure의 원리
45+
이게 어떻게 가능한 걸까?<br/>
46+
`(3)`에서 `outer` 함수의 실행 컨텍스트는 실행 컨텍스트 스택에서 제거되지만,<br/>
47+
**`outer` 함수의 렉시컬 환경만은 소멸하지 않기 때문**이다.
48+
49+
`outer` 함수의 렉시컬 환경은, `inner` 함수의 `[[Environment]]` 내부 슬롯이 참조하고 있고,<br/>
50+
`inner` 함수는 전역 변수 `innerFunc`이 참조하고 있으므로 가비지 컬렉션의 대상이 되지 않는다.<br/>
51+
따라서 중첩 함수 `inner`는 자신의 상위 스코프인 외부 함수 `outer`보다 오래 생존해 `outer`를 참조할 수 있으므로 `outer` 함수 내부의 식별자를 참조할 수 있게 된 것이다.
52+
53+
### Closure의 일반적 정의
54+
사실, 자바스크립트의 모든 함수가 상위 스코프를 기억하므로 이론적으로는 모든 함수가 Closure라고 할 수 있다.<br/>
55+
**그러나!!!** 일반적으로 우리가 Closure라고 부르는 건 다음과 같이 조건이 따른다.
56+
1. **중첩 함수****상위 스코프의 식별자를 참조**하고 있고,
57+
2. 중첩 함수가 **그 외부 함수보다 더 오래 유지**되는 경우에 한정해 Closure라고 부르는 것이 일반적이다.
58+
59+
참고로 Closure에 의해 참조되는 상위 스코프의 변수(위 예제로 치면 `outer` 함수의 지역 변수인 `x`)를 우리는 **Free Variable**(자유 변수)라고 부른다.
60+
61+
### Closure 최적화
62+
이론적으로 Closure는 상위 스코프를 기억해야 하므로 메모리가 낭비되진 않을까?하고 걱정할 순 있으나 그럴 필요는 없다.<br/>
63+
이는 모던 자바스크립트 엔진이 **최적화**를 통해 상위 스코프의 식별자 중에서 **기억해야 할 식별자만 골라 기억**하기 때문이다.<br/>
64+
따라서 Closure는 자바스크립트의 강력한 기능 중 하나로 알려져 있으며, 필요한 경우엔 적극적으로 활용하는 것이 좋다.
65+
66+
## 4. Closure의 활용
67+
Closure는 **State**(상태)를 안전하게 변경하고 유지할 때 많이 사용한다.<br/>
68+
즉, State를 안전하게 **information hiding**(은닉)하고, **특정 함수에게만 State 변경을 허용**하기 위해 사용한다.
69+
70+
예제를 살펴 보자.
71+
```javascript
72+
// 카운터 함수. 즉시 실행 함수로 작성했다.
73+
const increase = (function () {
74+
// 카운트 상태 변수
75+
let num = 0;
76+
77+
// 클로저
78+
return function () {
79+
// 카운트 상태를 1만큼 증가시킨다.
80+
return ++num;
81+
};
82+
}());
83+
84+
console.log(increase()); // 1
85+
console.log(increase()); // 2
86+
console.log(increase()); // 3
87+
```
88+
여기서 `increase`에 할당된 즉시 실행 함수는 호출된 직후 소멸되지만,<br/>
89+
**이 즉시 실행 함수가 반환한 Closure는 `increase` 변수에 할당**된다.
90+
91+
이때, **이 Closure는** 자신이 정의된 위치, 즉 상위 스코프를 기억하고 있다.<br/>
92+
즉, **좀 전에 소멸한 즉시 실행 함수의 렉시컬 환경을 기억하고 있다**는 뜻이다.<br/>
93+
따라서 이 Closure는 Free Variable인 **`num`을 언제든, 어디서든 참조하고 변경할 수 있게 되는 것**이다.
94+
95+
여기서, 이 즉시 실행 함수는 단 한 번만 실행되므로 `increase`가 호출될 때마다 `num` 변수가 재차 초기화될 일은 없다.<br/>
96+
또한 **`num` 변수**는 외부에서 직접 접근할 수 없는,<br/>
97+
**은닉된 private 변수**로서 기능할 수 있게 됐으므로 더 안정적인 프로그래밍이 가능해졌다.
98+
99+
이처럼 Closure는, State를 특정 함수에게만 접근을 허용해<br/>
100+
State 값을 안전하게 유지하고 변경하기 위해 사용할 수 있다.
101+
102+
## 5. Encapsulation and Data Hiding: 캡슐화와 정보 은닉
103+
Encapsulation(캡슐화)는 객체의 State를 나타내는 프로퍼티와</br>
104+
그 프로퍼티를 참조하고 조작하는 Behavior(동작)인 메서드를 하나로 묶는 행위를 뜻한다.
105+
106+
Data Hiding(정보 은닉)은 외부에 공개할 필요 없는 구현의 일부를 감춰 적절치 못한 접근으로부터 정보를 보호하는 것을 의미하며,</br>
107+
객체 간의 상호 의존성, 즉 Coupling(결합도)를 낮추는 효과가 있다.
108+
109+
대부분의 프로그래밍 언어는 `public`, `private`, `protected` 같은 Access Modifier(접근 제한자)를 제공해 공개 범위를 한정할 수 있는데,</br>
110+
자바스크립트는 이런 Access Modifier를 제공하지 않는다.</br>
111+
즉, 자바스크립트 객체의 모든 프로퍼티와 메서드는 기본적으로 public하다.
112+
113+
물론, 다음과 같이 필드 간 공개 범위를 달리하도록 설계를 할 수는 있다. 그러나 이런 패턴도 완전한 정보 은닉을 보장하지는 못한다.
114+
```javascript
115+
function Person(name, age) {
116+
this.name = name; // public
117+
let _age = age; // private
118+
}
119+
```
120+
다행히도, 2021년 쯤 private 필드를 정의할 수 있는 새로운 표준 사양이 제안됐다고 한다.</br>
121+
이는 추후 25.7.4절 'private 필드 정의 제안'에서 살펴 보자.
122+
123+
## 6. 자주 발생하는 실수
124+
생략. 자세한 내용은 책을 참고합시다!
125+
126+
끝.

0 commit comments

Comments
 (0)