< JavaScript 7편: 클로저 >

"클로저란 어떤 함수와 그 함수를 감싸고 있는 렉시컬 환경의 관계를 의미한다. 서로 다른 두 함수가 중첩되어 있을 때, 외부함수가 내부함수를 return하면 오직 내부함수를 통해서만 외부함수의 상태를 참조하거나 변경할 수 있다."

외부 변수를 기억하고 이 외부 변수에 접근할 수 있는 함수를 의미합니다.
호출이 끝난 후에도 여전히 도달 가능한 중첩 함수가 있을 수 있습니다. 이때는 이 중첩함수의 [[Environment]] 프로퍼티에 외부 함수 렉시컬 환경에 대한 정보가 저장됩니다. 도달 가능한 상태가 되는 것이죠.

  • 중첩된 함수는 다 클로저인건가?
    아니다. 두가지의 기준이 있다.
  1. 외부함수는 내부함수를 반환할 것
  2. 내부함수는 외부함수의 식별자를 하나 이상 참조할 것이라는 두가지의 기준이 존재한다. 이때 내부함수가 참조하는 외부함수의 식별자를 '자유 변수'라고 하며, 내부함수는 외부함수의 자유변수로부터 자유롭지 못하고 닫혀있기에 클로저라고 말하는 것이다. + 자바스크립트에서는 함수가 생성(정의)될 때마다 새로운 클로저가 만들어진다.
function outer() {
  const str = outer에서 정의한 상수;
  let num = 0;
  return function inner() {
    console.log(str);
    num++;
    console.log(num);
  }
}
  • 클로저는 그럼 어떻게 동작하는데?
    내부함수가 외부함수를 가리키는 스코프 체인을 형성한다. 여기서 주목할 것은 외부함수의 호출이 아니라 정의에 따라 내부함수의 상위 스코프가 결정되는데 이를 '렉시컬 스코프'라고 한다. 그렇게 하위함수는 상위 스코프 주소값을 항상 기억하고 있어, 언제나 자신이 정의된 시점에 존재하던 식별자를 참조할 수 있는 것이다.
    -> 오직 내부함수만을 통해서만 외부함수의 데이터를 조회/수정할 수 있다. 이렇게 접근이 제한된 형태를 private라고 한다. + 자바스크립트의 모든 함수는 [[Environment]]라는 내부슬롯에 자신이 정의된 상위 스코프의 참조(주소값)을 저장한다. 전역 실행 컨텍스트에서는 그것보다 더 높은 계층이 없으니 null이 담긴다.
  • 클로저를 사용하는 이유가 뭐야?
    원래였으면 스택에서 소멸될 함수가 상위 함수의 스코프가 여전히 참조되고 있다면, 그 환경은 실행 컨텍스트가 스택에서 소멸한 것과 관계없이 살아있으며 그 내부 데이터도 활용이 가능하다. 주위할 점은 같은 외부함수를 이용시, 단 한번만 호출되어야 같은 렉시컬 환경을 공유할 수 있다는 것이다.
  • 그렇다면 올바르게 사용하는 방법은?
    내부함수를 반환해서 그 내부함수를 품은 외부함수의 렉시컬 환경을 기억시키는 것이다.
  1. 잘못된 예시
function parent(manageMoney) {
  let assets = 99999;
  
  return function child() {
    console.log('기존 자산: ', assets);
    assets = manageMoney(assets);
    console.log('변경 자산: ', assets);
    return assets;
  }
}

function increase(money) {
  return money *= 100;
}

function decrease(money) {
  return money /= 100;
}

const increaser = parent(increase);
const decreaser = parent(decrease);

increaser();
increaser();
decreaser();
decreaser():

-> 클로저 관계는 형성되었으나, 서로 다른 함수를 할당 시 상위함수가 새로 호출된다. 이는 하위함수마다 상위함수의 렉시컬환경을 다르게 하여 전혀 다른 환경 레코드를 생성하기 때문에 상태를 공유할 수 없게 된다.

  1. 올바른 예시
const parent = (function() {
  let assets = 99999;
  
  return function child(manageMoney) {
    console.log('기존 자산: ', assets);
    assets = manageMoney(assets);
    console.log('변경 자산: ', assets);
    return assets;
  }
}());

function increase(money) {
  return money *= 100;
}

function decrease(money) {
  return money /= 100;
}

parent(increase);
parent(increase);
parent(decrease);
parent(decrease);

-> 상위함수가 반복 호출될 수 없도록, 즉시 실행함수로 감싼다. 내부함수를 즉시 실행 함수의 return값으로 설정하고 parent에 할당하면 앞으로 parent를 재사용하는 것은 즉시 실행함수의 반환값인 내부함수일 뿐이다.

+클래스가 필요한 이유 → 프로토타입객체를 통한 상속, 즉 프로토타입 메서드를 이용시 서로 다른 인스턴스(자식객체)끼리는 서로 다른 상태를 가져야하는데, 프로토타입 메서드(상위함수)를 공유하는 클로저의 특성상 모든 인스턴스가 같은 상태를 공유한다. 이러한 문제를 클래스가 해결해준다!

참고 링크
https://blog.naver.com/tombyun/222421802479
https://ko.javascript.info/closure#ref-212

좋은 웹페이지 즐겨찾기