Sitemap

Javascript — closure의 모든 것

8 min readAug 20, 2020

--

딱 간단히 말하면, closure라는 것은 “inner function이 자기가 속한 환경을 기억하는 함수”라고 볼 수 있다. 먼저 예시를 보자.

function outerFunc() {
let x = 100;
function innerFunc() {
return x;
}
return innerFunc();}let result = outerFunc();

위의 예시는 다음과 같이 구현되어있다.

  • outerFunc() 함수의 스코프 안에 x가 선언되어 있다.
  • outerFunc() 안에 innerFunc()가 존재한다.
  • innerFunc()는 outerFunc() 안에 있는 x값에 1을 더한다.
  • outerFunc()는 innerFunc()의 값을 return한다.
  • outerFunc()를 호출하여 result에 최종 값을 담는다.

이 예시에서는 최종 리턴 값으로 innerFunc()를 호출하여 그 “값”을 return하고 있다. 이 과정을 메모리 상에서 살펴보자.

우선 outerFunc()가 호출이 되면 stack에 outerFunc() 함수가 들어간다. 이 때, outerFunc와 관련된 변수들도 같이 들어간다.

outerFunc()가 실행되다가 innerFunc()가 호출되는 부분을 만나면 innerFunc()도 stack에 들어간다. 이 후, innerFunc()를 실행한다.

innerFunc()를 실행하면서 innerFunc() 밖, outerFunc()의 안에 있는 x 변수를 참조하게된다. 참조한 x에는 100이 들어있으므로 최종 값은 100을 return한다. 그리고 outerFunc()는 종료되므로, 그 안에 있던 변수 x도 가비지 콜렉터가 모두 청소한다.

이처럼 inner function은 밖의 function(Outer Function)의 스코프 내에서 존재하며, Outer Function이 종료되는 동시에 Outer Function안의 값에는 접근하지 못한다. 이미 Outer Function에 있는 값들은 가비지콜렉터가 청소를 했기 때문이다.

그러나, 클로저는 조금 다른 생김새를 가지고 있다.

function outerFunc() {
let x = 100;
function innerFunc() {
return x;
}
return innerFunc;}let result = outerFunc();
result();

위의 예시와 차이점은 위의 예시해서는 return을 “innerFunc()”로 해주고 있었다면, 클로저는 return을 “innerFunc”로 하고 있다. 이 두개의 차이점은 분명하다. return을 “innerFunc()”로 하는 것은 innerFunc() 함수를 호출하겠다는 의미이다. 호출해서 stack에 집어넣고 실행을 하겠다는 의미이다. 그러나 “innerFunc”로 return하는 것은 함수를 호출하는 것이 아니고, 함수 자체를 return하겠다는 것이다. 예시를 통해 두가지의 차이점을 보자.

function example() {
return "hi!";
}
let result1 = example();
let result2 = example;

example()이라는 function을 선언하고 return값을 “hi1!”라는 string값으로 설정했다. 이 때, result1은 함수를 “호출”했으므로 “hi!”라는 실제 값이 들어간다. 반면에, result2는 함수 그자체를 result에 넣어줬다. 한마디로, result2에는 example 함수의 참조값이 들어갔다는 것이다. 실제로 이 두가지를 출력해보면 다음과 같은 결과를 얻을 수 있다.

다시 클로저로 돌아와서, 클로저는 일반 inner Function과는 다르게 inner Function 그 자체를 return한다. 그 후에는, Outer Function이 종료되더라도 inner Function을 호출할 수 있다. 위 예제에서, result 변수를 result() 처럼 한 부분처럼 말이다.

근데 이게 어떻게 가능할까?

맨 처음에 클로저는 “inner function이 자기가 속한 환경을 기억하는 함수”이라고 했다. 어떻게 기억하고 있을까?

이는 매우 복잡한 과정을 거쳐서 이루어지는데, 먼저 실행 컨텍스트와 스코프체인의 개념을 알아야한다.

  • 실행 컨텍스트 : 함수가 실행되는 환경
  • 스코프 체인 : 스코프(유효범위)들을 담은 정보들을 관리

간단하게 말하면, 실행 컨텍스트란 함수가 실행되는 환경이다. 함수가 호출 되면 그 즉시 실행 컨텍스트가 만들어진다.

만들어진 이후에는, “활성 객체”라는 것이 만들어지고 그 안에 지역변수, this, inner function 등등이 들어가서 실행이된다.

이 때, 스코프 체인도 같이 만들어지는데, 스코프 체인의 가장 처음에는 현재 실행 컨텍스트의 활성 객체가 들어간다. 스코프 체인은 스코프, 즉 함수나 블록의 유효범위를 담은 정보들을 연결리스트 형식으로 관리한다.

안에 있는 inner function이 호출되었다면 inner function을 실행하기 위한 실행 컨텍스트가 또 하나 만들어지고, 스코프 체인도 같이 만들어진다.

위에서 보았던 클로저 예시 코드를 다시 한번 보면서 클로저가 어떻게 Outer Function이 종료되고 난 뒤에도 inner Function이 Outer Function의 값에 접근할 수 있는지 알아보자.

function outerFunc() {
let x = 100;
function innerFunc() {
return x;
}
return innerFunc;}let result = outerFunc();
result();

먼저, outerFunc()가 호출되었으므로, outerFunc() 실행 컨텍스트가 하나 생긴다. (그림에서 function x()라고 되어있는 부분은 function outerFunc()로 봐주시면 감사하겠습니다. 실수했네요ㅜㅜ)

(전역 컨텍스트는 프로그램이 실행되면 항상 가장 먼저 들어가있는 부분이다.) 실행 컨텍스트가 생긴 후, 활성 객체가 생기면서 x, innerFunc()가 들어가게된다. (지역 변수와 inner Function) 이 때, 스코프 체인은 전역 실행 컨텍스트의 활성 객체와, 현재 실행 컨텍스트인 outerFunc()의 활성 객체가 들어가있다.

outerFunc() 내부에는 innerFunc()가 있으므로 현재까지의 실행 관계는 이와 같다. outerFunc()는 실행이 종료되면 모두 반환되어 실행 컨텍스트 또한 없어진다.

들어간 후, innerFunc 자체를 return 했으므로, result()를 실행하면서 innerFunc() 실행 컨텍스트가 또 하나 생긴다. 이 때, innerFunc()는 outerFunc() 안에 있는 함수였으므로, 스코프 체인에 outerFunc()의 정보도 함께 가지고 있다.

따라서 result()가 실행되면서 outerFunc()의 실행 컨텍스트의 활성 객체도 갖고 있기 때문에 그 안의 지역변수나 함수들에 접근할 수가 있는 것이다.

맨 처음에 클로저를 “inner function이 자기가 속한 환경을 기억하는 함수” 라고 정의하였다. 스코프 체인, 실행 컨텍스트에 대한 내용을 좀 알면 기존의 그냥 inner function과 어떤 식으로 다른지 알 수 있고 왜 이렇게 정의했는지 그 내용도 알 수있을 것이다.

--

--

No responses yet