|
| 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