클로저의 개념
클로저란 기능은 배열이나 새기능을 만드는 것처럼 사용자가 명시적으로 사용하지 않는다. 특정 상황에서 자동으로 사용되며 클로저를 사용하고 있다고 이해하면 된다. 클로저를 이해하기 위해 아래 코드를 통해 예시를 살펴보자. secureBooking이라는 함수를 정의하였다. 이 함수는 passengerCount라는 변수를 가지며 이는 외부에서 조작하거나 접근할 수 없다. secureBooking 함수는 함수를 return하는데 상위에서 정의한 passengerCount 변수를 업데이트하는 것이다.
const secureBooking = function() {
let passengerCount = 0;
return function() {
passengerCount++;
console.log(`${passengerCount} passengers`)
}
}
const booker = secureBooking();
마지막 문장에서 "const booker = secureBooking()"라는 실행문을 통해 함수를 실행한다. 이 과정을 살펴보면 secureBooking 함수를 실행하기 위해 Global EC에서 먼저 등록된다. Global EC에 secureBooking 등록될 때 Global scope에도 포함되게 된다.
클로저의 동작 원리
그 후 secureBooking 함수가 실제로 실행될 때 새 실행 컨텍스트(EC)가 Call Stack의 맨 위에 놓이게 된다. 각각의 실행 컨텍스트(EC)에는 지역 변수를 포함하는 변수 환경(Variable Environment)이 포함되어 있다. 변수 환경(Variable Environment)은 함수의 범위이다. 이 실행 컨텍스트(EC)의 스코프 체인(Scope Chain)은 다음과 같다. passengerCount는 로컬 스코프(Local Scope)에 위치해있고 이 스코프의 모든 변수는 부모(Parent) 스코프에서 접근 가능하다. 이 경우 Global scope가 부모이다.
다음 줄을 보면 'return'을 통해 새로운 함수가 'booker' 변수에 저장된다. 이를 통해 Global EC에도 'booker' 변수가 포함되게 된다.
이제 secureBooking 함수가 최종적으로 'retrun'되면 실행 컨텍스트가 Call Stack에서 사라지게 된다. secureBooking 함수는 제 기능을 다했고 실행이 끝나게 되면서 사라진다. 지금까지 booker 함수가 어떻게 만들어지는지 이해하는 과정이었다. 이제 booker 함수를 사용해 실행 중인 클로저(Closures)를 확인해보자.
아래와 같이 booker 함수를 호출할 땜마다 passenger는 1, 2, 3명으로 증가하는 것을 확인할 수 있다. passenger 변수는 secureBooking 함수에 정의된 변수이고 이 함수는 이미 실행되어 Call Stack에서 사라졌는데 어떻게 이런 일이 가능할까? 이런 일을 가능하게 해주는 것이 바로 클로저(Closure)이다. 'booker' 함수는 단순히 전역 환경이나 전역 범위에 존재하는 기능이다. secureBooking 함수는 사라지고 없지만, booker 함수는 secureBooking가 생성된 당시 존재했던 변수에 계속 접근하고 있다. 클로저가 이런 역할을 한다. 클로저가 함수를 만들다고 할 때 함수의 생성될 때 존재했던 모든 변수를 기억한다. 이는 스코프 체인의 개념만으로 이해가 되지 않기 때문에 클로저도 함께 이해해야하는 이유이다. 클로저의 동작 방식을 깊이 있게 알아보자.
const secureBooking = function() {
let passengerCount = 0;
return function() {
passengerCount++;
console.log(`${passengerCount} passengers`)
}
}
const booker = secureBooking();
booker(); // 1 passengers
booker(); // 2 passengers
booker(); // 3 passengers
앞서 secureBooking의 실행 컨텍스트가 더 이상 Call Stack 존재하지 않는다는 사실을 확인했다. 이 상태에서 booker 함수를 호출하면 어떻게 되는지 확인해보자. booker는 Global scope에 위치한 기능이다. booker를 호출하면 Call Stack의 맨 위에 놓이며 해당 지역 변수가 비워지게 된다. 선언된 변수가 없기 때문이다. Scope의 경우 Global scope의 Child scope로 들어가게 된다. 스코프 체인에서는 어떠한 변수도 확인할 수 없다. 여기서 클로저의 역할을 알 수 있게 된다. 스코프는 어떤 함수라도 그 함수가 생성된 실행 컨텍스트의 변수 환경에 항상 접속할 수 있도록 해준다는 것이다.
즉, 클로저는 이미 사라진 secureBooking의 실행 컨텍스트(EC)의 변수 환경 접속하여 조작한다. passgerCount는 secureBooking의 실행 컨텍스트에서 만들어진 변수이다. 클로저는 secureBooking의 변수 환경에 접속하여 passgerCount를 조작하는 것이다. 이러한 연결고리를 만드는 것을 클로저이다.
1. A function has access to the variable environment (VE) of the execution context in which it was created. (실행 컨텍스트가 사라진 뒤에도 함수는 항상 자신이 생성된 실행 컨텍스트의 변수 환경에 접속할 수 있다)
2. Closure: VE attached to the function, exactly as it was at the time and place the funcction was created (클로저는 함수에 연결된 변수 환경이다. 함수가 생성된 때와 장소와 정확히 일치한다)
클로저의 개념을 이해하고 나면 실행 컨텍스트는 사리지고 없지만 변수 환경은 자바스크립트 엔진 어딘가에 존재한다는 것을 알 수 있다. 클로저 인해 함수가 탄생했을 때 존재했던 변수와 함수가 연결되어 있다. booker 함수의 실행 과정을 살펴보면서 클로저가 어떻게 동작하는지 더 자세히 알아보자.
booker 함수를 실행하면 passengerCount를 증가시키려고 한다. 하지만, booker scope에는 변수가 존재하지 않는다. 이에 Javascript는 즉시 클로저를 바라 보고 거기에서 변수를 찾일 수 있는지 확인한다. 이 과정은 Scope Chain보다 먼저 발생된다. 즉, Scope Chain보다 클로저가 우선이다.
클로저에서 가지고 있는 passengerCount 변수의 값이 0에서 1로 1이 증가하면서 booker EC는 Call Stack에서 사라진다.
두번째 booker 함수를 실행하면 클로저에 있는 passengerCount 1에서 2로 다시 1 증가하는 모습을 확인할 수 있다.
클로저의 개념 재정리
- A closure is the closed-over variable environment of the execution context in which a function was created, even after that execution context is gone.
- A closure gives a function access to all the vriables of its parent function, even after that parent function has returned. The function keeps a reference to its outer scope, which preserves the scope chain throughout time.
- A closure makes sure t hat a function doesn't loose connection to variables that existed at the function's birth place. (클로저는 고향과 연을 끊지 않는 사람에 비유할 수 있다. 사람은 함수이고 고향은 Parent scope라고 생각해보자)
- A closure is like a backpack that a function carries around wherever it goes. This backpack has all the variables that were present in the environment where the function was created. (클로저를 백팩에 비유하는 사람도 있다. 백팩안에 변수가 들어가 있다)
클로저의 유의사항
- We do NOT have to manually create closures, this is a JavaScript feature that happens automatically. We can't even access closed-voer variables explicitly. A closure is NOT a tangible JavaScript Object.
클로저는 일반적인 변수나 객체처럼 명시적으로 직접 접근 가능하지 않다. 클로저는 함수 내부의 속성(Property)일 뿐이다. 하지만, 클로저를 볼 수는 있다.
"console.dir(booker);"라고 입력을 하면 아래와 같이 Property가 나오며, [[Scopes]] 항목을 살펴보면 클로저를 확인할 수 있다. passengerCount가 3이기 때문에 booker 함수를 3번 실행했다는 것을 알 수 있다.
'Javascript' 카테고리의 다른 글
[JavaScript] 클로저(Closures) - 활용편(2/2) (0) | 2024.01.05 |
---|---|
[JavaScript] 클로저(Closures) - 활용편(1/2) (2) | 2024.01.04 |
[JavaScript] 비동기 코드의 동작 원리 - 이벤트 루프(The Event Loop) (2) | 2023.12.29 |
[JavaScript] AJAX와 API란 무엇인가? - 비동기 코드에 대한 이해 (0) | 2023.12.03 |
[JavaScript] 프로미스(Promise) Consume - rejected 처리 방법 (0) | 2023.11.27 |