본문 바로가기
자바스크립트/코어 자바스크립트

코어자바스크립트 5장<클로저>

by SONBAEJUN 2022. 8. 17.

1. 클로저의 의미 및 원리 이해

  • 위 코드에서 outer 함수 내부에 a라는 변수와 inner함수가 들어가 있고, outer 함수는 inner함수의 결과값을 반환한다.
  • 따라서 outer 함수의 실행컨텍스트가 종료되는 시점에선 변수 a를 참조하는 참조카운트가 0이 되고, 변수 a와 inner함수는 가비지 컬렉터의 수집 대상이 될 것이다.(참조 카운트가 0이므로)

  • 이제 위 코드르 보자, 이번엔 inner 함수의 결과값이 아닌 inner 함수 자체를 반환하고 있다.
  • 따라서 outer 함수의 실행 컨텍스트가 종료될때(8번줄) outer2 변수는 outer의 실행 결과인 inner 함수를 참조한다.
  • 이후 outer2를 호출하면 앞서 반환된 inner 함수가 실행되게 된다.
  • 그런데 outer 함수는 이미 실행이 종료 된 상태인데 inner 함수가 어떻게 outer함수의 LexicalEnvironment에 접근하냐?
  • 이는 가비지 컬렉터의 동작 방식 때문이다. 가비지 컬렉터는 어떤 값을 참조하는 변수가 하나라도 있다면 그 값은 수집 대상으로 포함시키지 않는다.
  • 위 코드에서 outer 함수는 inner 함수를 리턴하고, 외부함수인 outer 함수가 종료되더라도 내부 함수인 inner 함수는 언젠가 outer2함수를 실행시킴으로써 호출될 가능성이 열렸다. 따라서 가비지 컬렉터 수집 대상에서 제외되는 것이다

클로저는 어떤 함수에서 선언한 변수를 참조하는 내부함수에서만 발생하는 현상이다. 실행 컨텍스트가 종료 된 함수가 LexicalEnvironment에 속하는 변수가 가비지 컬렉터의 수집 대상에서 제외되는 경우는 지역변수를 참조하는 내부함수가 외부로 전달된 경우가 유일하단 말이다.

이를 바탕으로 클로저의 정의를 정확히 얘기하면 다음과 같다고 볼 수 있다.

 

"클로저란 어떤 함수 A에서 선언한 변수 a를 참조하는 내부함수 B를 외부로 전달할 경우 A의 실행 컨텍스트가 종료된 이후에도 변수 a가 사라지지 않는 현상이다"


2. 클로저의 메모리 관리

  • 클로저는 가비지 컬렉터의 대상에서 제외된다고 했다. 그 말인 즉슨, 메모리 누수의 위험이 있다는 얘기도 된다.
  • 하지만 개발자가 의도하여 가비지 컬렉터 수거 대상에서 제외시킨것을 '누수'라고 표현할 수 없으며, 과거 의도치 않게 누수가 발생하는 경우가 있었지만, 그 중 대부분은 최근의 JS엔진에선 발생하지 않거나 발견하기 힘들다.
  • 따라서 이제는 의도적으로 설계한 '메모리 소모'에 대해서만 잘 관리하면 되고, 관리하는 방법은 정말 심플하다.
  • 바로 "클로저의 필요성이 사라졌을 땐 더는 메모리를 소모하지 않게 하는 것" 이다. 그 말인 즉슨 가비지 컬렉터의 수집 대상에 포함시키는 것이고, 그 말인 즉슨 참조 카운트를 0으로 만들어 준단 얘기다.
  • 또한 참조 카운트를 0으로 만드는 방법은 식별자에 참조형이 아닌 기본형 데이터(보통 null 이나 undefined)를 할당하면 된다.  아래 코드와 같이 말이다.

10번줄에서 null을 부여함으로써 참조카운트를 0으로 만들어줬고, inner함수의 참조가 끊기게 되어 GC 수집대상이 될 것이다.


3. 클로저 활용 사례

3-1) 콜백 함수 내부에서 외부의 데이터를 사용하고자 할 때

  • 위 코드의 7번줄에서 addEventListener는 외부함수인 fruit 함수를 참조하고 있으며, fruit 함수의 종료 여부와 무관하게 클릭리스너 발생 시 이벤트 리스너의 outerEnvironment가 fruit의 LexicalEnvironment를 참조하게 된다.
  • 또한 참조 카운트가 발생했기 때문에 fruit 함수의 실행 컨텍스트가 끝나더라도 GC의 수집대상에서 제외되게 된다.

3-2) 접근 권한 제어(정보 은닉)

  • 정보 은닉은 어떤 모듈의 내부 로직에 대해 외부로의 노출을 최소화해서 모듈 간의 결합도를 낮추고 유연성을 높이고자 하는 현대 프로그래밍 언어의 중요한 개념 중 하나이다.
  • public, private, protected등의 종류가 있는데, JS는 기본적으로 변수 자체에 이러한 접근 권한을 직접 부여하도록 설계 돼 있지 않다. 하지만 클로저를 이용하면 함수 차원에서 public한 값과 private한 값을 구분 할 수 있다.
  • 예제 코드로 확인해보자

  • outer 함수를 종료할 때 inner함수를 반환함으로써 outer 함수의 지역변수인 a의 값을 외부에서도 읽을 수 있게 됐다.
  • 이처럼 클로저를 활용하면 외부 스코프에서 함수 내부의 변수들 중 선택적으로 일부의 변수에 대한 접근 권한을 줄 수 있다. 바로 return을 활용해서 말이다.
  • 기본적으로 outer 함수는 외부(전역 스코프)로 부터 철저하게 닫힌 공간이다. 외부 공간에 노출 돼 있는 outer 라는 변수로 함수 실행을 할 순 있지만, outer 함수 내부엔 어떠한 개입도 할 수 없다. 외부에서는 오로지 outer함수가 return 한 정보에만 접근이 가능한 것이다.
  • 따라서 return을 통하여 접근 권한 제어가 가능하다. return 값은 public 아닌 값은 private로 말이다.

3-3) 부분 적용 함수

  • 부분 적용 함수란 n개의 인자를 받는 함수에 미리 a개의 인자만 넘겨 기억시켰다가, 나중에 (n-a)개의 인자를 넘기면 비로소 원래 함수의 실행 결과를 얻을 수 있게끔하는 함수이다.

  • 위 코드에선 addPartial 함수가 인자 5개를 미리 적용하고, 추후 추가적으로 인자들을 전달하면 모든 인자를 모아 원래의 함수가 실행되는 부분 적용 함수이다.
  • 다만 this의 값을 변경할 수 밖에 없기 때문에 메서드에서는 사용할 수 없을 것 같다. this에 관여하지 않는 별도의 부분 적용 함수가 있다면 범용성 측면에서의 향상이 기대된다.

  • 첫번째 인자에는 원본 함수를, 두번째 인자 이후부터는 미리 적용할 인자들을 전달하고, 반환할 함수(부분 적용 함수)에서는 다시 나머지 인자들을 받아 이들을 한데 모아(concat) 원본 함수를 호출(apply)한다.
  • 미리 일부 인자를 넘겨두어 기억하게끔 하고 추후 필요한 시점에 기억했던 인자들까지 함께 실행하게 한다는 개념 자체가 클로저의 정의에 정확히 부합한다.

3-4) 커링 함수

  • 커링 함수란 여러개의 인자를 받는 함수를 하나의 인자만 받는 함수로 나눠서 순차적으로 호출될 수 있게 체인 형태로 구성하는 것을 말한다.
  • 부분 적용함수와 기본적인 맥락은 일치하지만, 차이점은 커링은 한번에 하나의 인자만 전달하는 것을 원칙으로 하는 것과, 중간 과정상의 함수를 실행한 결과는 그 다음 인자를 받기 위해 대기만 할 뿐으로, 마지막 인자가 전달되기 전까지는 원본 함수가 실행 되지 않는다.

필요한 인자 개수만큼 함수를 만들어 계속 리턴해주면 된다

ES6부턴 화살표를 사용해 가독성을 향상 시킬 수 있다

화살표함수를 이용해 가독성을 향상시킴