본문 바로가기
  • 프론트엔드 개발자 세오세오 | Frontend dev Seo
Learn to Code

[JS] 클로저(Closure)란?

by CEOSEO 2021. 4. 10.
728x90
반응형

출처: https://edward-huang.com/

 

클로저(Closure)란?

자바스크립트를 공부하는 분들께, 자바스크립트를 공부하며 맞닥드렸던 여러 개념들 중 이해하는데 애를 먹었던 것이 무엇이었냐고 묻는다면 가장 자주 등장하는 개념 리스트에 클로저(closure)가 필수로 들어가 있을 것이다. 그만큼 이해하기가 어려웠던 개념인데, 오랜 시간을 들여 들여다본 결과 이전보다 1 정도 이해 정도가 늘어난 것 같아 블로그에 포스팅하려고 한다.

 

 

우선, 나에게 있어 클로저에 대한 이해가 어려웠던 것엔 MDN의 클로저에 대한 정의부터가 굉장히 난해했다는 사실도 한몫했다.

"A closure is the combination of a function and the lexical environment whitin which that function was declared."
"클로저란 함수와 그 함수가 선언된 렉시컬 환경의 조합이다." ????

 

언뜻 보았을 땐 그래서 도대체 이게 무슨 말인가 싶었다. 하지만 더 공부를 하다보니, 저 문장이 말하고자하는 바가 무엇인지 어느 정도 이해가 되는 것 같았다. 그런데 그렇다고해서 저 문장의 난해함이 사라지는 것은 아니기에, 나의 머리 속 이해를 조금 더 명확하게 해줄 다른 정의를 내릴 필요성을 느낄 수 밖에 없었다.

 

그래서 내 스스로가 클로저를 이해하기 위해 만든 클로저의 정의는 다음과 같다:

클로저(Closre)란 외부 함수의 생명주기가 종료되었음에도 불구하고 외부함수보다 중첩함수가 더 오래 유지되는 경우 외부함수의 변수를 참조할 수 있는 중첩함수를 뜻한다.

클로저(Closre)란 다른 함수 안에 정의된내부 함수로서
(1) 외부 함수의 식별자를 참조하고, 동시에
(2) 외부 함수보다 생명주기가 긴 중첩함수이다.

 

(1)과 (2)가 정확히 뭘 뜻하는지는 아래 예제들을 보면서 설명하도록 하겠다.

 

 

 

 

 

예제 1. 다른 함수 안에 있긴 하지만 상위 스코프의 식별자를 참조하지 않는 경우

 

functoin funOne() {
  const a = 1
  const b = 2
  
  function funTwo() {
    const c = 3
    consol.log(c) // 다른 함수 안에 있긴한데 상위 스코프의 식별자를 참조하지 않았다. 너의 a b 따위 내 알바 아니다
  }
  
  return funTwo
}

const two = funOne()
two()

 

위 예제는 funOne()이라는 함수 안에 funTwo()라는 함수가 만들어져있는 모양을 보여준다. 처음 클로저라는 개념을 들었을 때 "다른 함수 안에 있는 함수면 다 클로저라는거야?"라는 생각을 했었었다. 그에 대한 답은 당연히 '아니오'이다.

 

위 예제를 보면, funTwo()의 상위 스코프는 funOne()이 된다. 그래서 funTwo()는 funOne()의 a와 b 변수를 참조할 수 있다. 하지만 우리의 funTwo()는 그러지 않았다. 이렇듯 다른 함수 안에 정의되어 있긴 하지만 그 위 상위 스코프의 식별자를 참조하지 않았을 경우엔 클로저로 분류하지 않는다.

 

 

 

예제 2. 리턴되어 세상 밖으로 나오지 않았을 때

 

function funOne() {
  const a = 1

  function funTwo() {
    console.log(a) // 이번엔 상위 스코프의 식별자를 참조했다
  }
  
  funTwo()
}

funOne()

 

이번 예제같은 경우, funTwo()가 드디어 마이페이스를 벗어나 상위 스코프의 x를 참조했다. 그렇다면 클로저일까? 정답은 아니오이다.이런 경우는 일반적으로 클로저라고 하지 않는다. 왜일까?

 

그 이유는, 바로 중첩함수인 funTwo()가 리턴되지 않았기 때문이다. (두둥..!) 리턴되어 바깥 공기를 맛보지 못하였기 때문에, funTwo()는 상위 스코프의 식별자를 참조했음에도 불구하고 그 생명 주기가 외부 함수인 funOne()보다 짧았다. (RIP) 중첩함수가 진정한 클로저로 거듭나려면(??) 생명 주기가 끝난 외부 함수의 식별자를 참조할 수 있어야 하는데, 안타깝게도 이번 예제의 funTwo()는 그러지 못했다.

 

 

 

예제 3. 상위 스코프 식별자도 참조하면서 외부 함수보다 생명 주기가 긴 경우

 

function funOne() {
  const a = 1
  const b = 2
  
  function funTwo() {
    console.log(a) // 참조도 했다
  }
  
  return funTwo // 리턴도 되었다
}

const two = funOne()
two()

 

위 예제의 funTwo()는 드디어 클로저에 해당한다. 외부 함수의 a도 갖다썼겠다, funOne()에서 리턴되어 프레시한 바깥 공기도 맛보게 되었다. 이와 같이, 클로저는 (1) 상위 스코프의 식별자를 참조하면서 (2) 외부 함수보다 더 오래 유지되는 경우를 일컫는다.

 

한가지 추가로 알아둘 점은, 클로저의 경우에 우리의 브라우저들은 최적화를 위하여 상위 스코프의 식별자 중 클로저가 참조하는 식별자들만을 기억한다는 것이다. 즉, 위 예제의 경우에 funOne() 안에서 정의된 변수는 a와 b 두 개가 있는데, 클로저인 funTwo()는 a 하나만을 참조했다. 그래서, funTwo()의 생명주기가 살아있는 동안 a는 기억되지만 b의 존재는 잊혀진다는 것이다. 이처럼 클로저가 참조했기 때문에 기억되어진 식별자는 자유 변수(free variable)이라고 부른다.

 

 

참고: 자바스크립트의 메모리 관리

 

 

 

728x90
반응형

댓글