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 function’s 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
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
예시코드 1을 일부 수정하였다.
goodGuy 함수에서 내부 함수 callMeByName 함수의 실행값을 리턴하고 있다.
역시 내부함수는 외부함수의 변수를 참조하고 있지만, 여기도 역시 Closure 는 존재하지 않는다.
이 코드도 마찬가지로 goodGuy 함수가 실행 종료되면,(함수 실행컨텍스트 종료) lexical environment의 변수들(name, times, callMeByName)에 대한 참조가 사라진다.
🤷🏻♀️ Closure 는 무엇이 다를까?
🔍 이제 실제 Closure 예시를 살펴보자.
👉🏼 For Example 3
위의 코드는 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
외부 함수 celebrityName 의 리턴 값을 가진 mjName 변수가 호출되면 내부 함수 lastName을 실행된다.
이 내부 함수 lastName는 celebrityName 함수의 매개변수 firstName를 포함하여 외부 함수의 변수 nameIntro를 참조할 수 있다.
이 celebrityName 함수는 Closure 가 된다.
🔖 Additional
Closures have access to the outer function’s 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를 사용하려면, 외부 함수 안에서 함수를 정의하고, 그 정의된 내부 함수를 외부로 전달한 후 사용한다.
여기서 내부 함수의 외부 전달이 꼭 함수 자체를 리턴하는 것만 해당하는 것은 아니다.
함수를 다른 함수에 전달하는 것도 하나의 방법이다.
🔗 함수 리턴의 예
위의 예시를 다시 살펴보자.
내부 함수 callMeByName는 외부 함수 goodGuy의 리턴값으로 외부로 전달되었다.
callMeByName2가 외부에서 호출될 때마다 Closure가 발생한다.
🔗 함수 전달의 예
setInterval 함수에 인자로 내부 함수 inner를 전달하거나, addEventListener에 인자로 clickHandler 함수를 전달하는 방식 역시 Closure 를 발생시키는 방식이다.
🔖 Additional
즉시 실행 함수 표현 (IIFE)
IIFE 는 즉시 실행 함수 표현 (Immediately Invoked Function Expression)의 약자이다. 쉽게 말해 함수 선언과 동시에 즉시 실행되는 함수를 의미한다.
“이 안에 들어있는 코드를 바로 실행해라” 라는 뜻으로 이해하면 빠르다.
(function () {
// ...do something...
})();
전역 스코프에 불필요한 변수를 추가해서 오염시키는 것을 방지할 수 있을 뿐 아니라 IIFE 내부 안으로 다른 변수들이 접근하는 것을 막을 수 있는 방법이다.
📍 Closure를 생성하는데 많이 사용된다.
👉🏻 다음 장에서 이어서 설명 👉🏻 Next Page