Closure

Closure

코어 자바스크립트(정재남 저) 책을 기본으로 하여 자바스크립트를 공부하면서 내용을 정리하고자 한다.


05 Closure

📯 Closure의 정의


MDN에서 말하기를,

A closure is the combination of a function bundled together (enclosed) with references
to its surrounding state (the lexical environment).

🕵🏻‍♀️ Closure 는 그것의 주변 상태(the lexical environment)의 references 와 함께 번들로 묶인 함수의 조합 이라고 설명한다.

여기서 the lexical environment는 실행 컨텍스트에서의 Outer(Reference to the outer environment) 를 의미하고, 이 Outer 에 의해 형성된 Scope의 참조와 같이 묶인 함수 정도로 이해하면 되지 않을까 싶다.


그리고, MDN에는 이런 설명이 추가로 있다.

In other words, a closure gives you access to an outer functions scope from an inner function.

🕵🏻 Closure 를 사용하면 내부 함수에서 외부 함수의 Scope에 액세스할 수 있다.


그리고, MDN에는 이런 추가 설명도 하나 더 있다.

 In JavaScript, closures are created every time a function is created, at function creation time.

🕵🏻‍♂️ 자바스크립트에서는 함수가 생성될 때마다 Closure가 생성된다.



🔦 Closure 가 어떤 상황에서 발생하는지 좀 더 알아보자.

👉🏼 For Example 1

closure


1. goodGuy 함수에서 name, times 변수를 선언하고, goodGuy 함수의 내부 함수인 callMeByName 는 내부에서 선언된 변수 없이
그냥 `console.log`를 실행한다.

2. callMeByName 함수의 `console.log`를 실행하려면 lexical environment에 변수 name, times가 있어야 하는데
해당 환경에는 변수가 존재하지 않는다.

따라서, Outer에서 지정된 상위 환경, 즉 외부함수 goodGuy 함수의 lexical environment에서 해당 변수를 찾게 된다.

3. 해당 변수이 확인되면 내부함수의 `console.log`는 상위 환경의 변수를 참조하여 실행하게 된다.

4. goodGuy 함수가 실행 종료되면,(함수 실행컨텍스트 종료)
lexical environment의 변수들(name, times, callMeByName)에 대한 참조가 사라진다.

훗날 참조카운트가 없는 변수들은 가비지 컬렉터의 수집대상이 된다. [__DataType2의 내용 참고]

위의 예시는 외부함수의 내부함수에서 외부함수의 변수를 참조하는 예이다.

Closure 의 정의 중 Closure 를 사용하면 내부 함수에서 외부 함수의 Scope에 액세스할 수 있다. 고 하였는데,

위의 예시는 Closure에 해당하는 상황이 아니다.

🤾🏻 다른 예시를 살펴보자.

👉🏼 For Example 2

closure2

예시코드 1을 일부 수정하였다.

goodGuy 함수에서 내부 함수 callMeByName 함수의 실행값을 리턴하고 있다.

역시 내부함수는 외부함수의 변수를 참조하고 있지만, 여기도 역시 Closure 는 존재하지 않는다.

이 코드도 마찬가지로 goodGuy 함수가 실행 종료되면,(함수 실행컨텍스트 종료) lexical environment의 변수들(name, times, callMeByName)에 대한 참조가 사라진다.



🤷🏻‍♀️ Closure 는 무엇이 다를까?

🔍 이제 실제 Closure 예시를 살펴보자.

👉🏼 For Example 3

closure3

위의 코드는 goodGuy 함수에서 callMeByName 함수 자체를 리턴하고 있다.

이에 따라 callMeByName2 변수에는 함수 자체가 할당되고, callMeByName2가 호출될 때마다 값이 변화하며 출력되는 것을 볼 수 있다.


🕵🏻‍♀️ CHECK POINT 🕵🏻‍♂️


🤔 callMeByName2 변수에 goodGuy 함수의 결과값이 담길 때, 이미 goodGuy 함수는 결과값을 변수에 할당함과 동시에 실행이 완료되었다. 실행컨텍스트가 종료되었다는 것인데, 어떻게 계속 함수를 호출할 수 있는 것인가? (어떻게 실행 종료된 외부함수의 변수를 참조하는가?)


이는 GC(garbage Collector)의 동작 방식 때문이다.

GC는 어떤 값을 참조하는 변수가 있다면 그 값을 수집 대상으로 포함하지 않는다.

goodGuy 함수가 종료되면서 반환된 callMeByName 함수는 callMeByName2가 언제라도 호출되면 참조해야 하는 값이다.

그렇기 때문에 참조카운트가 존재하는 이상 GC의 대상이 되지 않는 callMeByName 함수는 지속적으로 사용 가능해진다.


바로 이 3번째 예시에서의 상황이 Closure 에 해당하는 상황이다.

goodGuy 함수를 보통 쉽게 Closure라고 부른다.


🧩 Closure 추가 예시를 살펴보자.

👉🏼 For Example 4

closure4

외부 함수 celebrityName 의 리턴 값을 가진 mjName 변수가 호출되면 내부 함수 lastName을 실행된다.

이 내부 함수 lastName는 celebrityName 함수의 매개변수 firstName를 포함하여 외부 함수의 변수 nameIntro를 참조할 수 있다.

이 celebrityName 함수는 Closure 가 된다.



🔖 Additional

Closures have access to the outer functions variable even after the outer function returns


Closure의 정의에서 다소 부족한 점을 추가로 설명하자면,

Closure 를 사용하면 내부 함수에서 외부 함수의 Scope에 액세스할 수 있다. 의 정확한 의미는

클로저는 외부 함수가 반환된 후에도 외부 함수의 변수에 액세스할 수 있다. 이다.

🧺 내부 함수에서 외부 함수의 Scope에 액서스하는 모든 상황이 아니라, 외부 함수가 반환된 후에도 외부 함수의 변수에 액세스 할 수 있을 경우, 그 상황 혹은 현상을 Closure 라고 부른다.

💡 NOTICE

이 현상을 일으키는 주요 대상인 함수 자체를 통상적으로 Closure 라고 많이 얘기하지만, 개념적으로는 이런 현상 그 자체가 Closure 라고 보는 것이 맞다.



🪁 Closure의 방식

To use a closure, define a function inside another function and expose it.

To expose a function, return it or pass it to another function.

🪄 Closure를 사용하려면, 외부 함수 안에서 함수를 정의하고, 그 정의된 내부 함수를 외부로 전달한 후 사용한다.

여기서 내부 함수의 외부 전달이 꼭 함수 자체를 리턴하는 것만 해당하는 것은 아니다.

함수를 다른 함수에 전달하는 것도 하나의 방법이다.


🔗 함수 리턴의 예

위의 예시를 다시 살펴보자.

closure5

내부 함수 callMeByName는 외부 함수 goodGuy의 리턴값으로 외부로 전달되었다.

callMeByName2가 외부에서 호출될 때마다 Closure가 발생한다.


🔗 함수 전달의 예

closure6

closure7

setInterval 함수에 인자로 내부 함수 inner를 전달하거나, addEventListener에 인자로 clickHandler 함수를 전달하는 방식 역시 Closure 를 발생시키는 방식이다.



🔖 Additional

즉시 실행 함수 표현 (IIFE)

IIFE 는 즉시 실행 함수 표현 (Immediately Invoked Function Expression)의 약자이다. 쉽게 말해 함수 선언과 동시에 즉시 실행되는 함수를 의미한다.

“이 안에 들어있는 코드를 바로 실행해라” 라는 뜻으로 이해하면 빠르다.


(function () {
  // ...do something...
})();


전역 스코프에 불필요한 변수를 추가해서 오염시키는 것을 방지할 수 있을 뿐 아니라 IIFE 내부 안으로 다른 변수들이 접근하는 것을 막을 수 있는 방법이다.

📍 Closure를 생성하는데 많이 사용된다.


코드 참고하려면


👉🏻 다음 장에서 이어서 설명 👉🏻 Next Page



참고자료