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-1장 클로저 - 이나린 #997

Merged
merged 1 commit into from
Dec 12, 2023
Merged
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
120 changes: 120 additions & 0 deletions docs/24_클로저/이나린.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# 🎯 24 클로저

[24.1 렉시컬 스코프](#1-렉시컬-스코프)
[24.2 함수 객체의 내부 슬롯 [[Environment]]](#2-함수-객체의-내부-슬롯-environment)
[24.3 클로저와 렉시컬 환경](#3-클로저와-렉시컬-환경)

## 0. 클로저

`클로저는 함수와 그 함수가 선언된 렉시컬 환경과의 조합이다.`

```javascript
const x = 1;

function outerFunc() {
const x = 10;
function innerFunc() {
console.log(x); // 10
}

innerFunc();
}

outerFunc();
```

위 예제에서 outerFunc 함수에서 중첩 함수 innerFunc가 정의 및 호출되었다.
이때 중첩 함수 innerFunc의 상위 스코프는 외부 함수 outerFunc의 스코프다. 따라서 중첩 함수 innerFunc 내부에서 자신을 포함하고 있는 외부 함수 outerFunc의 x 변수에 접근할 수 있다.

만약 innerFunc 함수가 outerFunc 함수의 내부에서 정의된 중첩 함수가 아니라면 innerFunc 함수를 outerFunc 함수의 내부에서 호출한다 하더라도 outerFunc 함수의 변수에 접근할 수 없다.

```javascript
const x = 1;

function outerFunc() {
const x = 10;
innerFunc();
}

function innerFunc() {
console.log(x); // 1
}

outerFunc();
```

👉🏻 이런 현상이 나타나는 이유는 자바스크립트가 **렉시컬 스코프**를 따르기 때문이다.

## 1. 렉시컬 스코프

가장 중요한 것은 함수를 어디서 호출했는지가 아니라 함수를 **어디에 정의**했는지에 따라 상위 스코프를 결정한다는 것이며, 이를 렉시컬 스코프라 한다.

스코프의 실체는 실행 컨텍스트의 렉시컬 환경이다. 이 렉시컬 환경은 자신의 외부 렉시컬 환경에 대한 참조를 통해 상위 렉시컬 환경과 연결된다. 이것이 바로 스코프 체인이다.

렉시컬 환경의 외부 렉시컬 환경에 대한 참조에 저장할 참조값 = 상위 렉시컬 환경에 대한 참조이며, 이것이 상위 스코프이다.

렉시컬 스코프를 다음과 같이 정의하자.

**렉시컬 환경의 외부 렉시컬 환경에 대한 참조에 저장할 참조값, 즉 상위 스코프에 대한 참조는 함수 정의가 평가되는 시점에 함수가 정의된 환경에 의해 결정된다.**

## 2. 함수 객체의 내부 슬롯 [[Environment]]

함수가 정의된 위치와 호출되는 위치는 다를 수 있기에, 렉시컬 스코프가 가능하려면 함수는 상위 스코프를 항상 기억해야 한다. 이를 위해 함수는 자신의 내부슬롯 [[Environment]]에 자신이 정의된 환경인 상위 스코프의 참조를 저장한다.
저장된 상위 스코프의 참조는 현재 실행 중인 실행 컨텍스트의 렉시컬 환경을 가리킨다.

예를 들어, 전역에서 정의된 함수 선언문은 전역 코드가 평가되는 시점에 평가되어 함수 객체를 생성한다. 이때 생성된 함수 객체의 내부 슬롯 [[Environment]]에는 함수 정의가 평가되는 시점, 즉 전역 코드 평가 시점에 실행 중인 실행 컨텍스트의 렉시컬 환경인 전역 렉시컬 환경의 참조가 저장된다.

**함수 객체는 내부 슬롯 [[Environment]]에 저장한 렉시컬 환경의 참조, 즉 상위 스코프를 자신이 존재하는 한 기억한다.**

## 3. 클로저와 렉시컬 환경

```javascript
const x = 1;

function outer() {
const x = 10;
const inner = function () {
console.log(x);
};
return inner;
}

const innerFunc = outer();
innerFunc();
```

outer 함수 호출 → outer 함수는 중첩 함수 inner 반환 → 생명 주기 마감 → outer 함수의 실행 컨텍스트가 스택에서 제거됨 → outer 함수의 지역 변수 x 생명 주기 마감 → x 변수 접근 가능??

위의 코드 실행 결과는 outer 함수의 지역 변수 x의 값인 10이다. 이미 생명 주기가 종료되어 제거된 변수 x가 나온 것이다.

외부 함수보다 중첩 함수가 더 오래 유지되는 경우 중첩 함수는 이미 생명 주기가 종료한 외부 함수의 변수를 참조할 수 있다. 이러한 중첩 함수를 **클로저**라 부른다.

위의 예제 코드를 다시 살펴보자.

1. outer 함수가 평가되어 함수 객체를 생성할 때 현재 실행 중인 실행 컨텍스트의 렉시컬 환경, 전역 렉시컬 환경을 outer 함수 객체의 [[Environment]] 내부 슬롯에 상위 스코프로서 저장한다.

2. outer 함수를 호출하면 outer 함수의 렉시컬 환경이 생성되고 앞서 outer 함수 객체의 [[Environment]] 내부 슬롯에 저장된 전역 렉시컬 환경을 outer 함수 렉시컬 환경의 외부 렉시컬 환경에 대한 참조에 할당한다.

3. 중첩 함수 inner가 평가되면 중첩 함수 inner는 자신의 [[Environment]] 내부 슬롯에 현재 실행 중인 실행 컨텍스트의 렉시컬 환경, 즉 outer 함수의 렉시컬 환경을 상위 스코프로서 저장한다.

4. outer 함수의 실행이 종료하면 inner 함수를 반환하면서 outer 함수의 생명 주기가 종료된다. 스택에서 제거되면 outer 함수의 실행 컨텍스트는 스택에서 제거되지만 **outer 함수의 렉시컬 환경까지 소멸하는 것은 아니다.**
outer 함수의 렉시컬 환경은 inner 함수의 [[Environment]] 내부 슬롯에 의해 참조되고 있고 inner 함수는 전역 변수 innerFunc에 의해 참조되고 있으므로 가비지 컬렉션의 대상이 되지 않기 때문이다. 가비지 컬렉터는 누군가 참조하고 있는 메모리 공간을 함부로 해제하지 않는다.

5. outer 함수가 반환한 inner 함수를 호출하면 inner 함수의 실행 컨텍스트가 생성되고 스택에 푸시된다. 렉시컬 환경의 외부 렉시컬 환경에 대한 참조에는 inner 함수 객체의 [[Environment]] 내부 슬롯에 저장되어 있는 참조값이 할당된다.

중첩 함수 inner는 외부 함수 outer보다 더 오래 생존했다. 이때 중첩 함수는 자신이 정의된 위치에 의해 결정된 상위 스코프를 기억한다. 중첩 함수 inner의 내부에서는 상위 스코프를 참조할 수 있으므로 상위 스코프의 식별자를 참조할 수 있고 식별자의 값을 변경할 수도 잇다.

자바 스크립트의 모든 함수는 상위 스코프를 기억하므로 이론적으로 모든 함수는 클로저이지만, 일반적으로 모든 함수를 클로저라고 하지는 않는다.

클로저가 아닌 경우

- **참조하지 않는 식별자**를 기억하는 것은 메모리 낭비이기 때문에 대부분의 모던 브라우저는 최적화를 통해 상위 스코프를 기억하지 않는다.
- 중첩 함수가 클로저였지만 외부 함수보다 일찍 소멸되면 생명 주기가 종료된 외부 함수의 식별자를 참조할 수 있다는 클로저의 본질에 부합하지 않기 때문에, 중첩 함수는 클로저라 하지 않는다.

클로저인 경우

- 외부 함수보다 중첩 함수가 더 오래 유지되는 경우 중첩 함수는 이미 생명 주기가 종료한 외부 함수의 변수를 참조할 수 있다. 이러한 중첩 함수를 클로저라 부른다.
- 클로저는 중첩 함수가 상위 스코프의 식별자를 참조하고 있고 중첩 함수가 외부 함수보다 더 오래 유지되는 경우에 한정하는 것이 일반적이다.

**✨ 자유 변수**
클로저에 의해 참조되는 상위 스코프의 변수
Loading