Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

24-2장 클로저 - 강철원 #1015

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 154 additions & 0 deletions docs/24_클로저/강철원B.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,158 @@ innerFunc(); // ④ 10
`outer` 함수의 실행이 종료하면 inner함수를 반환하면서 outer 함수의 생명 주기가 종료됩니다.(3)
즉, `outer` 함수의 실행 컨텍스트가 실행 컨텍스트 스택에서 제거됩니다. 이때 `outer` 함수의 실행 컨텍스트는 실행 컨텍스트 스택에서 제거되지만 `outer` 함수의 렉시컬 환경까지 소멸하는 것은 아닙니다.

## 4. 클로저의 활용

클로저는 상태를 안전하게 변경하고 유지하기 위해 사용합니다.
상태를 안전하게 은닉하고 특정 함수에게만 상태 변경을 허용하기 위해 사용합니다.(캡슐화)

```js
// 카운트 상태 변수
let num = 0;

// 카운트 상태 변경 함수
const increase = function () {
// 카운트 상태를 1 만큼 증가 시킨다.
return ++num;
};

console.log(increase()); //1
console.log(increase()); //2

```

위코드는 오류를 발생시킬 가능성을 내포하고 있는 좋지 않은 코드입니다.
이유는 다음과 같습니다.
1. 카운트 상태(num 변수의 값)는 `increase`함수가 호출되기 전까지 변경되지 않고 유지되어야 한다.
2. 이를 위해 카운트 상태(num 변수의 값)는 `increase`함수만이 변경할 수 있어야 한다.

하지만 카운트 상태는 전역 변수를 통해 관리되고 있기 때문에 언제든지 누구나 접근할 수 있고 변경할 수 있습니다. 이는 의도치 않게 상태가 변경될 수 있다는 것을 의미합니다. 만약 누군가에 의해 의도치 않게 카운트 상태, 즉 전역 변수 `num`의 값이 변경되면 이는 오류로 이어집니다.

이때 클로저를 사용하의 위의 문제를 관리할 수 있습니다.
```js
// 카운트 상태 변경 함수
const increase = (function () {
// 카운트 상태 변수
let num = 0;
// 클로저
return function () {
// 카운트 상태를 1만큼 증가시킨다.
return ++num;
};
}());

console.log(increase()); //1
console.log(increase()); //2
```

이를 더 발전시켜 자유 변수인 `num`을 공유하고 증가와 감소를 함께 작동시키는 코드의 예는 아래와 같습니다.

```js
// 함수를 반환하는 고차 함수
// 이 함수는 카운트 상태를 유지하기 위한 자유 변수 counter를 기억하는 클로저를 반환한다.
const counter = (function(){
//카운트 상태를 유지하기 위한 자유 변수
let counter = 0;

//함수를 인수로 전달받는 클로저를 반환
return function (aux) {
//인수로 전달받은 보조 함수에 상태 변경을 위임한다.
counter = aux(counter);
return counter;
}
}());

//보조함수
function increase(n) {
return ++n;
}

//보조함수
function decrease(n){
return --n;
}

//보조 함수를 전달하여 호출
console.log(counter(increase)); //1
console.log(counter(increase)); //2

//자유 변수를 공유한다.
console.log(counter(decrease)); // 1
console.log(counter(decrease)); // 0
```
## 5. 캡술화와 정보 은닉
캡슐화는 객체의 상태를 나타내는 프로퍼티와 프로퍼티를 참조하고 조작할 수 있는 메서드를 하나로 묶는 것을 말합니다. 캡슐화는 객체의 특정 프로퍼티나 메서드를 감출 목적으로 사용하기도 하는데 이를 정보 은닉이라고 합니다.
정보 윽닉은 외부에 공개할 필요가 없는 구현의 일부를 외부에 공개되지 않도록 감추어 적절치 못한 접근으로부터 객체의 상태가 변경되는 것을 방지해 정보를 보호하고, 객체 간의 상호 의존성, 즉 결합도를 낮추는 효과가 있습니다.
대부분의 객체지향 프로그래밍 언어는 클래스를 정의하고 그 클래스를 구성하는 멤버(프로퍼티와 메서드)에 대하여 `public`, `privated`, `protected`같은 접근 제한자를 선언하여 공개 범위를 한정할 수 있습니다.
`public`으로 서언된 프로퍼티와 메서드는 클래스 외부에서 참조할 수 있지만 `private`으로 선언된 경우는 클래스 외부에서 참조할 수 없습니다.
자바스크립트는 `public`, `private`, `protected`같은 접근 제한자를 제공하지 않습니다. 따라서 자바스크립트 객체의 모든 프로퍼티와 메서드는 기본적으로 외부에 공개되어 있습니다. 즉, 객체의 모든 프로퍼티와 메서드는 기본적으로 `public`입니다.
결국 자바스크립트는 정보 은닉을 흉내낼 수는 있지만 완전하게 지원하지 않습니다.
## 6. 자주 발생하는 실수
```js
var funcs = [];

for (var i = 0; i < 3; i++) {
funcs[i] = function () { return i; }; // ⓐ
}

for (var j = 0; j < funcs.length; j++) {
console.log(funcs[j]()); // ⓑ
}
```

첫 번째 코드 블록 내에서 함수가 `funcs` 배열의 요소로 추가됩니다.
두 번째 코드 블록에서는 추가된 함수를 순차적으로 호출합니다.
이때 호출되는 `i`는 전역 변수`i`의 값을 호출하게 되므로 `i`는 첫 번째 블록에서 3 값으로 마무리 됩니다. 따라서 3이 세 번 호출됩니다.

따라서 클로저를 사용해 원하는 형태로 바르게 동작하는 코드를 만들면 이는 아래와 같습니다.

```js
var funcs = [];

for (var i = 0; i < 3; i++){
funcs[i] = (function (id) { // ⓐ
return function () {
return id;
};
}(i));
}

for (var j = 0; j < funcs.length; j++) {
console.log(funcs[j]());
}
```
ⓐ에서 즉시 실행 함수는 전역 변수 `i`에 현재 할당되어 있는 값을 인수로 전달받아 매개변수 id에 할당한 후 중첩 함수를 반환하고 종료됩니다. 즉시 실행 함수가 반환하는 함수는 `funcs`배열에 순차적으로 저장됩니다.

이때 즉시 실행 함수의 매개 변수 id는 즉시 실행 함수가 반환한 중첩 함수의 상위 스코프에 존재합니다. 즉시 실행 함수가 반환한 중첩 합수는 자신의 상위 스코프를 기억하는 클로저이고, 매개변수 id는 즉시 실행 함수가 반환한 중첩 함수에 묶여있는 자유 변수가 되어 그 값이 유지됩니다.

아래와 같이 `let`을 사용하여더 깔끔하게 코드를 만들 수 있습니다.
```js
const funcs = [];

for (let i = 0; i < 3; i++) {
funcs[i] = function () { return i; };
}

for (let i = 0; i < funcs.length; i++) {
console.log(funcs[i]()); // 0 1 2
}
```

또 다른 방법으로는 고차 함수를 사용하는 방법이 있습니다. 이 방법은 변수와 반복문의 사용을 억제할 수 있기 때문에 오류를 줄이고 가독성을 좋게 만듭니다. 이로 표현한 코드는 아래와 같습니다.

```js
// 요소가 3개인 배열을 생성하고 배열의 인덱스를 반환하는 함수를 요소로 추가한다.
// 배열의 요소로 추가된 함수들은 모두 클로저다.
const funcs = Array.from(new Array(3), (_, i) => () => i); // (3) [ƒ, ƒ, ƒ]

// 배열의 요소로 추가된 함수 들을 순차적으로 호출한다.
funcs.forEach(f => console.log(f())); // 0 1 2
```

📚 읽고나서

- 클로저를 최근에 거의 사용한적이 없어서 까먹고 있었다. 라이브러리 코드를 보다보면 가끔 마주치기는 해도 많이 사용되는 것을 보지 못했다. 앞으로 공부하다가 정말 딱맞게 사용된 코드를 본다면 여기에 Reference로 추가해둬야겠다고 생각했다.

- 프리코스를 하면서 종종 `_`를 변수 앞에 붙이신 분들으 보았는데 `5 캡슐화와 정보 은닉`을보고 근본적인 해결책이 되지 못한다는 것을 조금 더 확실하게 알게 되었다.
- 클로저를 공부하면서 이전에 실행 컨텍스트와 스코프에 대해 왜 먼저 알아야 했는지 알게되었다.
- 클로저를 활용하면 조금 더 함수형 프로그래밍을 수월하게 할 수 있을 것 같다.
Loading