[JavaScript] 클로저 Closure

sujpark·2022년 5월 14일
1

정의

클로저란 ?

클로저는 자바스크립트 고유의 개념이 아니다.
함수를 일급객체로 취급하는 함수형 프로그래밍 언어에서 공통적으로 사용되는 특성이다.
따라서 ECMAScript 사양에 정의되어있지 않다.
대신 MDN에 따른 클로저의 정의는 다음과 같다.

클로저함수그 함수가 선언된 렉시컬 환경과의 조합이다.

렉시컬 환경? 렉시컬 환경과의 조합? 처음 읽을 때는 무슨 말인지 도통 이해가 가질 않는다.
정의를 이해하기 위해 먼저 아래 코드들을 살펴보자

// 1. inner 함수가 outer 함수 바깥에서 정의된 경우
const x = 1;

function outer() {
	const x = 10;
  	inner();
}

function inner() {
	console.log(x);
}

outer(); // 1
// 2. inner 함수가 outer 함수 내부에서 정의된 경우
const x = 1;

function outer() {
	const x = 10;
    function inner() {
	  console.log(x);
    }

    inner();
}

outer(); // 10

코드를 실행해보면, inner 함수가 정의된 위치에 따라 참조하는 변수 x 가 달라지는 것을 확인할 수 있다.
outer 함수 외부에서 inner 함수를 선언했을 때는 outer 외부의 변수 x 를 참조하고,
outer 함수 내부에서 inner 함수를 선언했을 때는 outer 내부의 변수 x 를 참조한다.

자바스크립트는 정적 스코프를 따르는 언어이다. (대부분의 언어가 그렇다)
정적 스코프를 따르는 언어는 함수가 선언된 위치에 따라 상위 스코프가 정해진다.
즉 어디에 함수를 정의했는지에 따라서 상위 스코프가 정해진다.

또한 자바스크립트에서 스코프라는 개념은 렉시컬 환경 을 통해 구현된다.
렉시컬 환경 이 스코프의 실체라고 할 수 있다.

쉽게 보는 정의

MDN에 의한 클로저의 정의를 다시 한번 보자

클로저함수그 함수가 선언된 렉시컬 환경과의 조합이다.

위에서 말했듯이 정적 스코프를 따르는 언어는 함수가 선언된 위치에 따라 상위 스코프(상위 렉시컬 환경)가 정해진다.
따라서 정의를 쉽게 바꾸면 다음과 같다.

클로저함수그 함수의 상위 스코프와의 조합이다.

그래서 함수와 그 함수의 상위 스코프를 어떻게 조합할 수 있을까?

const x = 1;
function outer() {
  const x = 10;
  const inner = function () {
    console.log(x);
  }
  return inner;
}

const inner = outer(); // ...(*)
inner(); // 10

위 코드는 outer 함수 외부와 내부에 각각 다른 값을 가지는 변수 x를 선언하고,
중첩함수 inner 로 하여금 console.log 를 통해 x 를 출력하게 만든다.
(*) 에서 outer 함수의 호출 후 outer 함수의 실행이 끝나면 outer 함수의 생명주기도 종료되어
outer 함수 내부 변수 x 에 접근할 수 없을 것 처럼 보인다.

하지만 코드를 실행해보면 내부 변수 x의 값인 10을 출력한다.
이는 outer 함수의 스코프(렉시컬 환경)가 아직 유효하다는 것을 의미한다.

사실 참조되고 있는 렉시컬 환경은 해당 문맥의 생명주기가 종료되어도 가비지 컬렉터에 의해 메모리가 해제되지 않는다.
outer 함수는 outer 함수의 스코프를 외부 스코프로 참조하는 inner 함수를 반환해서 저장했기 때문에 outer 함수의 렉시컬 환경은 사라지지 않았다.

이처럼 중첩함수가 외부함수보다 오래 유지되는 경우
중첩함수는 이미 생명주기가 종료된 외부함수의 변수를 참조할 수 있는데
이 중첩함수를 클로저라고 한다.


활용

클로저는 특정 함수에게만 상태 변경을 허용함으로써 상태가 의도치 않게 변경되는 것을 막는다.

잘못된 예시

let num = 0;

const increase() {
  return ++num;
}

console.log(increase()); // 1
console.log(++num); // 2
console.log(increase()); // 3

위 코드는 increase 함수가 변수 num 의 상태를 변경하고 있지만,
++num 처럼 다른 코드에 의해 변수 num 의 상태가 변경될 수 있는 위험을 가지고 있다.


const increase() {
  let num = 0;
  return ++num;
}

console.log(increase()); // 1
console.log(increase()); // 1
console.log(increase()); // 1 

위 코드는 함수를 사용할 때마다 변수 num 이 새롭게 선언되기 때문에 의도한 대로 사용할 수 없다.

올바른 예시

const increase =( function () {
  let num = 0;
  return function () {
  	return ++num;
  }
}())

console.log(increase()); // 1
console.log(increase()); // 2
console.log(increase()); // 3

변수 num 을 은닉하고 increase 함수에 의해서만 상태가 변경되는 올바른 예시이다.

아래는 decrease 함수까지 제공하는 예시이다.

const counter = (function () {
  let num = 0;
  return {
    increase() {
      return ++num;
    },
    decrease() {
      return num > 0 ? --num : 0;
    }
  }
}());

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

참고

이웅모, 『모던 자바스크립트 Deep Dive - 자바스크립트의 기본 개념과 동작 원리』, 위키북스(2020), p388-408

profile
JavaScript TypeScript React Next.js

0개의 댓글