클로저

se-een·2023년 1월 4일
0
post-thumbnail

이 글은 '이웅모'님의 '모던 자바스크립트 Deep Dive' 책을 통해 공부한 내용을 정리한 글입니다. 저작권 보호를 위해 책의 내용은 요약되었습니다.

클로저(Closure)

함수를 일급 객체로 취급하는 함수형 프로그래밍 언어의 특성으로, 함수와 그 함수가 선언되었을 때의 렉시컬 환경과의 조합을 말한다. 자바스크립트 엔진은 함수를 어디에서 정의했는지에 따라 상위 스코프를 결정한다. 이를 렉시컬 스코프라고 한다.

함수가 정의된 환경과 호출된 환경은 다를 수 있으므로 렉시컬 스코프가 가능하려면 자신의 상위 스코프를 기억해야한다. 따라서, 함수는 자신의 내부 슬롯 [[Environment]]에 상위 스코프를 저장한다. 이후 함수가 호출될 때 자바스크립트 엔진은 실행 컨텍스트의 스코프 체인을 따라 호출된 함수의 렉시컬 스코프를 조회할 수 있다.

예시를 살펴보자.

function outer() {
  const age = 20;
  const inner = function () { console.log(age); };
  return inner;
}

const inner = outer();
inner(); // 20

outer 함수는 내부함수 inner를 반환하고 생을 마감했지만 outer의 변수 age를 여전히 inner 함수가 참조하는 것을 볼 수 있다. 위와 같이, 자신을 포함하고 있는 외부함수보다 내부함수가 더 오래 유지되는 경우, 외부 함수 밖에서 내부함수가 호출 되더라도 외부함수의 지역 변수에 접근할 수 있는 함수를 클로저라 부른다.

추가로 클로저에 의해 참조되는 외부함수의 변수, 위 예제에서는 변수 age자유변수(Free Variable)이라고 부른다.

위 코드를 그림으로 보면 다음과 같다.

클로저의 활용

클로저가 가장 유용하게 사용되는 상황은 현재 상태를 기억하고 변경된 최신 상태를 유지하는 경우이다.

function counter(func) {
  let count = 0;
  return function () {
    count = func(count);
    return count;
  };
}

function increase(state) {
  return ++state;
}

function decrease(state) {
  return --state;
}

// increaser와 decreaser는 독립된 렉시컬 환경이므로 상태가 연동되지 않음
const increaser = counter(increase);
console.log(increaser()); // 1
console.log(increaser()); // 2

const decreaser = counter(decrease);
console.log(decreaser()); // -1
console.log(decreaser()); // -2

클로저를 활용하면 다음과 같은 이점을 얻을 수 있다.

  • 전역 변수 사용 억제
  • 정보 은닉

위 예시에서 클로저를 활용하지 않고 동일한 기능을 구현하려면 count란 변수를 전역 변수로 선언하여 활용하는 방법밖에 없다. 전역 변수는 언제든지 누구나 접근할 수 있으며 변경 또한 가능하다는 위험성이 있다. 즉, 누군가 의도치 않게 count란 전역 변수를 수정한다면 이는 오류로 이어질 수 있다.

위 예시에서 countprivate한 변수이다. increase 또는 decrease 함수가 호출될 때만 변경이 가능하며 외부에서는 직접 접근할 수 없다. 이는 전역 변수로 사용했을 때 의도치 않은 변경을 걱정할 필요가 없어지며 보다 안정적인 프로그래밍이 가능하다.

상태 변경이나 가변(mutable) 데이터를 피하고 불변성(Immutability)을 지향하는 함수형 프로그래밍에서 부수 효과(Side effect)를 최대한 억제하여 오류를 피하고 프로그램의 안정성을 높이기 위해 클로저는 적극적으로 사용된다.

위 예시를 다음과 같이 생성자 함수로 구현하여 클로저를 활용할 수 있다.

function Counter() {
  // 자유 변수 count
  let count = 0;
  
  this.increase = function() {
    return ++count;
  };
  
  this.decrease = function() {
    return --count;
  };
}

const counter = new Counter();

console.log(counter.increase()); // 1;
console.log(counter.decrease()); // 0;

생성자 함수 Counter에 변수 count는 인스턴스 프로퍼티가 아닌 변수이다. 따라서 외부에서 접근할 수 없기에 private하다. 또한 this.increasethis.decrease는 클로저이기에 렉시컬 환경인 생성자 함수 Countercount 변수에 접근이 가능하다. 추가로 이 두 메서드는 동일한 렉시컬 환경인 생성자 함수 Counter에 속하기에 변수 count를 공유한다.

profile
woowacourse 5th FE

0개의 댓글