본문 바로가기
Study/JavaScript & Typescript

[JS] 클로저(Closure)와 렉시컬 스코핑

by DawIT 2021. 12. 17.
320x100

Javascript를 어느정도 사용해 보았다면 클로저 라는 단어를 한번쯤은 들어보았을 것이다. 그러나 실제로 클로저가 정확이 어떤 것이고, 어떤 상황에 쓰이는지 애매한 점이 있어서 정리하려고 한다.

 

먼저 MDN에서는 클로저를 다음과 같이 설명하고 있다.

 

클로저는 함수와 함수가 선언된 어휘적 환경의 조합이다. 클로저를 이해하려면 자바스크립트가 어떻게 변수의 유효범위를 지정하는지(Lexical scoping)를 먼저 이해해야 한다.

 

함수함수가 선언된 어휘적 환경의 조합 이라고 설명하고 있다. 이를 이해하기 위해 렉시컬 스코핑을 먼저 훑어본다.

 

어휘적 범위 지정(Lexical Scoping)

 

자바스크립트에서 상단과 같은 코드가 있다고 할때, 콘솔에는 2가 찍힌다는것을 쉽게 예측할 수 있다. 기본적으로 함수는 자신의 스코프 내에서 변수를 찾기 때문이다. 그렇다면 여기서 inner 함수의 내부에 있는 num을 삭제하면 어떻게 될까?

 

 

자바스크립트는 성공적으로 1을 출력한다. 이는 자바스크립트의 어휘적 범위 지정(렉시컬 스코핑, Lexical Scoping)의 한 예이다. 자바스크립트는 함수가 중첩된 상황에서 자신의 함수 내에서 해당 변수를 찾을 수 없다면 바깥 함수의 범위(Scope)에까지 접근할 수 있다.

 

 

물론 이렇게 여러번 중첩된 경우에도 계속 바깥 변수를 참조할 수 있다.

 

여기까지 이해하는데 그리 어렵지 않다. 그리고 여기까지 이해했다면 클로저가 무엇인지 이해하는 것도 그리 어렵지 않다.

 

클로저(Closure)

그러면 다음과 같은 코드에서는 어떤 결과를 얻을 수 있을까? 

 

 

outer 함수는 inner 함수를 선언 및 반환하는데, 논리적으로 생각했을때 outer 함수가 리턴한 후에는 outer 함수 스코프에 있던 num 변수는 없어지고 함수를 호출하면 Error를 뱉지 않을까?

 

 

실제로 실행해보면, 1을 정상적으로 뱉는것을 볼 수 있다. 이것이 바로 클로저다. outer 함수는 inner 함수를 반환함으로써 닫혔(Closed) 지만, 그렇다고 해서 자신이 가지고 있던 어휘적 환경(Lexical Scope) 까지는 없애지 않는다.

 

또한 이런것도 가능하다.

 

 

방금 함수가 리턴과 함께 닫히긴 하더라도, 그 어휘적 환경은 가지고 있는다고 했다. 따라서 위와 같이 처음에 인자를 주고 해당 인자를 저장하는 다른 함수를 만든 뒤, 이를 사용하는것도 가능하다.

 

MDN에서는 sumArgOne과 같은 함수를 함수를 만들어내는 공장 이라고 표현했다.

어디에 쓰는가?

이제 클로저가 어떤것인지는 알겠는데, 그러면 어떻게 쓸 수 있을까? 방금 '함수를 만들어내는 공장' 이라는 표현을 했는데, 이를 활용해서 MDN에서는 다음과 같은 예제를 보여주고 있다.

 

 

프론트엔드에서 DOM 요소에 이벤트를 부여할 때, 이벤트 핸들러 함수를 넘기게 된다. 여기에서는 글자 크기를 변경하는 예시를 들고 있다. body의 글자 크기를 할당하는 함수를 반환하는 함수를 정의하고, 각각 12, 14, 16 px에 대해서 함수를 만들어 핸들러로 등록하고 있다.

 

private 흉내내기

ES6 이전에는 class가 존재하지 않아서 프로토타입 기반으로 클래스를 흉내내어 사용했다. 클로저가 여기에서 private method/property 를 구현하는데 도움을 줄 수 있다.

 

 

상단 예제에서 makeObject 함수는 자기 자신의 스코프로써 privateMethod와 privateNumber를 가지고 있다. 그리고 Object literal으로 하나의 오브젝트를 반환하고 있는데, 해당 오브젝트에서는 publicNumber와 set, get 메서드가 보인다.

 

이렇게 구현하면 하단 실행부에서는 생성한 myObject에서 publicNumber는 바로 접근 및 수정할 수 있지만, privateMethod 혹은 privateNumber에 접근하려고 하면 TypeError를 던지게 된다. 이렇게 해서 클로저를 통해 private을 구현할 수 있는 것이다!

 

클로저에 관해 MDN에 정말 잘 설명되어 있으니 참고하면 좋을 것 같다.

댓글