2 minute read

클로저

클로저는 자바스크립트에서만 쓰이는 개념이 아닌,

함수를 일급 객체로 취급하는 함수형 프로그래밍 언어에서 사용되는 특성이다.

자바스크립트에서만 쓰이는 개념이 아니기 때문에 ECMAScript 사양에는 등장하지 않고,

MDN에서는 아래와 같이 정의한다.

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

한글로 쓰여있지만, 도대체 무슨 말인지 알 수 없다.

예제를 보면서 이해해보자.

const x = 1;
const funcA = () => {
  const x = 10;
  funcB();
};
const funcB = () => {
  console.log(x); // 1
};

funcA();

위의 예제의 경우에는 funcA 함수와 funcB 함수가 각각의 스코프를 구성하고 있기 때문에

funcB 함수에서는 funcA 함수의 x를 참조하지 못하고, 전역 스코프의 x를 참조해 1을 반환한다.

const x = 1;
const funcA = () => {
  const x = 10;
  const funcB = () => {
    console.log(x); // 10
  };
  funcB();
};
funcA();

하지만 위의 예제에서는, funcB 함수의 상위 스코프 funcA 함수의 x를 참조할 수 있기 때문에,

10을 반환하게 된다.

이러한 현상대로 실행되는 이유는, 자바스크립트가 ‘렉시컬 스코프’를 따르는 프로그래밍 언어이기 때문이다.

그럼 렉시컬 스코프는 무엇일까?

렉시컬 스코프

‘함수를 어디서 호출했는지’에 따라 상위 스코프를 결정하는 것을 동적 스코프,

‘함수를 어디서 정의했는지’에 따라 상위 스코프를 결정하는 것을 렉시컬 스코프 또는 정적 스코프라고 한다.

동적 스코프는 함수를 호출하는 위치에 따라 상위 스코프가 변하지만,

렉시컬 스코프는 함수가 정의된 위치에 따라 상위 스코프가 결정되기 때문에 정적 스코프라고 부른다.

자바스크립트를 비롯한 대부분의 프로그래밍 언어는 이 렉시컬 스코프를 따른다.

어쨋든, 자바스크립트는 렉시컬 스코프를 따르므로 함수가 호출된 위치는 상위 스코프의 결정에

어떠한 영향도 끼치지 않는다. 다시 말하면, 함수의 상위 스코프는 언제나 자신이 정의된 스코프다.

const x = 1;
const funcA = () => {
  const x = 10;
  funcB();
};
const funcB = () => {
  console.log(x);
};
funcA(); // 1
funcB(); // 1

위 예제의 funcA와 funcB 함수는 전역에서 정의되었고, 함수의 상위 스코프는

함수가 어디서 정의되었냐에 따라 결정되므로 두 함수의 상위 스코프는 전역이다.

렉시컬 스코프를 정리하자면,

→ 상위 스코프에 대한 참조는 함수가 정의된 위치에 의해 결정된다, 이것이 렉시컬 스코프이다.

라고 할 수 있다.

그렇다면, 클로저의 예제를 한번 살펴보도록 하자.

const addFunc = (num) => {
  const addNum = (add) => {
    return num + add;
  };
  return addNum;
};

const add1 = addFunc(1);
console.log(add1(2)); // 3

addFunc 함수는 addNum 함수를 return한다.

addNum 함수는 addFunc 함수의 하위 스코프이다.

이 떄 addNum의 스코프를 살펴보면, Local로 숫자 2(add)를 간직하고 있고,

‘클로저’로 숫자 1(num)을 간직하고 있다.

개발자도구로 디버깅을 돌려보면,

위의 내용을 확인해볼 수 있다.

결국 클로저를 정리해보자면,

외부 함수(addFunc)보다 내부 함수(addNum)가 더 오래 유지되는 경우에,

이미 생명주기가 종료된 외부 함수(addFunc)의 변수를 참조할 수 있는 것을 클로저라고 부르는 것이다.

Updated: