자바스크립트 클로저(closure)의 개념

이동준·2023년 7월 25일
0

자바스크립트

목록 보기
16/28

클로저(closure)의 개념

클로저는 사실 자바스크립트 고유의 개념이 아니라 함수를 일급 객체로 취급하는 함수형 프로그래밍 언어에서 사용되는 중요한 특성이다.
자바스크립트의 고유 개념이 아니므로 ECMAScript 명세에 클로저의 정의는 등장하지 않는다.

“A closure is the combination of a function and the lexical environment within which that function was declared.”
클로저는 함수(외부 함수)와 그 함수(내부 함수)가 선언됐을 때의 렉시컬 환경과의 조합이다.

참고 MDN 을 보면 클로저에 대해 이렇게 정의하고 있다. 위 정의에서 중요한 키워드는 함수가 선언되었을 때의 렉시컬 환경이다.

위 정의에서 말하는 함수란 반환된 내부함수를 의미하고, 그 함수가 선언될 때의 렉시컬 환경이란 내부 함수가 선언되었을 때의 스코프를 의미한다. 즉, 클로저는 반환된 내부함수가 자신이 선언됐을 때 환경인 스코프를 기억하여 자신이 선언됐을 때의 환경 밖에서 호출되어도 그 환경에 접근 할 수 있는 함수를 말한다.
클로저는 자신이 생성될 때의 환경을 기억하는 함수라고 말할 수 있다.

클로저에 의해 참조된 외부함수의 변수는 자유 변수라고 부르고, 클로저라는 이름은 자유 변수에 함수가 닫혀있다라는 의미로, 의역하면 자유 변수에 엮여있는 함수라는 뜻이 되겠다.

실행 컨텍스트의 관점에서 설명하면,내부 함수가 유효한 상태에서 외부 함수가 종료하여 외부 함수의 실행 컨텍스트가 반환되어도, 외부 함수 실행 컨텍스트 내의 활성 객체는 내부 함수에 의해 참조되는 한 유효하여 내부 함수가 스코프 체인을 통해 참조할 수 있는 것을 의미한다.

즉, 외부 함수가 이미 반환 되었더라도 외부 함수 내의 변수는 이를 필요로 하는 내부 함수가 하나 이상 존재하는 경우 계속 유지된다는 것이다. 이때 내부 함수가 외부 함수에 있는 변수의 복사본이 아니라, 실제 변수에 접근한다는 것에 주의해야한다.

클로저를 사용하는 이유

클로저는 생성될 때의 렉시컬 환경을 기억해야 하므로 메모리 차원에서는 손해를 볼 수 있다. 하지만 클로저는 자바스크립트의 강력한 기능으로 이를 적극적으로 사용해야만 한다. 이유는

전역 변수를 줄일 수 있다.

전역 변수가 많으면 어디서든 실수로라도 접근 할 수 있기 때문에 최대한 전역 변수는 줄여서 코드를 작성하는 편이 좋다.
하지만 프로그램을 구현하다보면 함수 하나에서만 사용하는데 전역 변수가 필요한 순간이 있다. 이럴 경우 클로저가 유용하게 활용된다.

const btn = document.querySelector('button')

btn.addEventListener('click',handleClick)

let count = 0
function handleCilck(){
  count++
  return count
}

위 예시와 같은 경우에 count 를 전역 변수로 사용해줘야 count 가 증가하게끔 해줄 수 있다. 하지만 클로저를 사용한다면 해결 할 수 있다.

const btn = document.querySelector('button')

btn.addEventListener('click',handleClick())

function handleCilck(){
  let count = 0
  return function (){
    count++
    return count
  }
}

외부함수 handleClick() 의 렉시컬 환경을 참조하는 함수(무명)를 btn 의 콜백 함수로 이용해 전역 객체 없이 구현 할 수 있다.

비슷한 형태 코드의 재사용률을 높힐 수 있다.

const newTag = function(open, close) {
    return function(content) {
        return open + content + close
    }
}

const bold = newTag('<b>', '</b>')
const italic = newTag('<i>', '</i>')

console.log(bold(italic("This is my content!")))
//<b><i>This is my content!</i></b>

bold , italic 과 같은 새로운 태그를 만들 수 있는 함수 newTag() 를 클로저를 이용해 간단하게 구현 할 수 있었다.
인자에 open , close , content 를 한번에 다 받는다면, "This is my content!" 와 같은 값을 출력하고 싶을 때 가독성이 떨어질 수 있다.
하지만 클로저로 구현한다면, 가독성도 좋고 재사용하기도 편한 코드를 구현 할 수 있다.

착각하기 쉬운 클로저

function outer() {
  let name = 'kyle';
  if (true) {
    let city = 'seoul';
    return function inner() {
      console.log(city);
    };
  }
}

위 예시 코드는 당연하게도 클로저가 아니다.
클로저는 내부에서 선언된 함수가 외부 함수의 지역 변수를 사용해 줬을 경우에만 클로저라고 선언된다.
내부 함수에도 클로저를 사용하고 싶다면 name 변수를 사용해주면 된다.

function outer() {
  let name = 'kyle';
  if (true) {
    let city = 'seoul';
    return function inner() {
      console.log(city);
      console.log(name);
    };
  }
}

이렇게 외부 함수의 선언된 변수를 내부 함수에서 사용하면 클로저를 사용 할 수 있게 된다.

추가

function say() {
  const a = 1;
  const b = 2;
  const c = 3;
  
  function log() {
    console.log (a + b);
  }
  
  return log;
}

const foo = say();

foo();

c 는 가비지 컬렉터에 의해서 수거가 된다. ab 는 반환되는 시점에 외부로 전달되어 참조가 되기 때문에 ab 는 저장되지만, c 는 사용되지 않을 것으로 고려돼, 버려지게 된다.

function say() {
  const a = 1;
  const b = 2;
  const c = 3;
  
  function log() {
    // console.log (a + b);
  }
  
  return log;
}

const foo = say();

foo();

만약 ab 도 참조되지 않는다면, 클로저는 사라지게 된다. 이유는 위와 같이 내부 함수에서 참조하는 값이 없기 때문에 아무것도 저장되지 않고 사라진다.

간단 : 외부 함수에 선언된 변수를 내부 함수에서 참조한 것을 외부로 전달을 하면 값이 사라지지 않는다. 그걸 클로저라고 한다.

0개의 댓글