제이크's 자바스크립트 팁 #2, Closure 제대로 알고가자

Jake Seo·2020년 1월 26일
2

Jake's javascript tips

이전까지는 자바스크립트 개발자라면 알아야 할 33가지 컨셉에 대한 번역글을 주로 올렸었다. 지금 다시보면 굉장히 허접한 번역글인데, 번역 이후에 무언가 나만의 컨텐츠를 진행하고 싶다는 욕심이 생겨서 자바스크립트에서 내가 헷갈렸던 부분만 따로 다시 공부하여 정리하도록 할 것이다.

그 둘째 시간 Closure.

Closure(클로저)란 무엇일까?

JavaScript | MDN의 정의에 따르면 클로저는 다음과 같습니다.

클로저는 함수와 함수가 선언된 어휘적 환경의 조합이다. 클로저는 독립적인 변수를 참조하는 함수이다. (독립적인 변수란 로컬에 선언되지도 않고, 파라미터로 넘겨지지도 않는 변수를 말한다.) 클로저에 선언된 함수는 생성되었을 때의 환경을 기억한다.

그리고 아래에 다음과 같은 말도 있습니다.

클로저를 이해하려면 자바스크립트가 어떻게 변수의 유효범위를 지정하는지(Lexical scoping)를 먼저 이해해야 한다.

친숙하지 않은 단어들이 마구 나와서 한번 봐서는 쉽게 위의 정의가 와닿지 않습니다.

일단 차근차근 개념부터 알고 넘어가봅시다.

어휘적 환경(Lexical Environment)?

어휘적 환경이란 영어로 표현하면, Lexical Environment입니다. 도대체 어휘적 환경이 무엇일까 생각하는 분들이 많을 수 있는데, 간단하게 생각하면, 정적인(변하지 않는) 환경입니다. 어휘적 환경(변하지 않는 환경)을 코드 예제와 함께 살펴보며 이해해봅시다.

하지만, 코드를 살펴보기 전에 잠깐 가장 중요한 개념 하나를 알고 가봅시다. 그것은 바로 함수가 참조하는 변수는 함수가 선언되는 순간 지정된다는 것입니다.

아래 코드는 전역 name을 참조하는 함수의 예제입니다.

let name = 'jake seo';
function lexical() { 
	console.log(name);
}
lexical();

이 경우에 lexical함수는 선언 당시 전역 블록의 name 변수에 들어있던 'jake seo'라는 문자열을 기억하는 것이 아니라, 전역 블록의 name 변수를 참조하게 됩니다. 실제 브라우저 콘솔에서 실행시켜봅시다.

위와 같은 결과가 나옵니다. 전역 블록에 존재하는 name의 내용을 바꾸면, 바꾼 내용이 출력되는 것을 알 수 있습니다.

어휘적 환경(정적인 환경)을 이용한 Closure(클로저)

이제 클로저의 정의 뒷부분을 다시 살펴봅시다.

클로저는 독립적인 변수를 참조하는 함수이다. (독립적인 변수란 로컬에 선언되지도 않고, 파라미터로 넘겨지지도 않는 변수를 말한다.) 클로저에 선언된 함수는 생성되었을 때의 환경을 기억한다.

일단 클로저의 실체는 '함수'입니다. '함수'인데, 독립적인 변수를 참조하는 함수라고 합니다. 그리고 독립적인 변수는 로컬에 선언되지도 않고, 파라미터로 넘겨지지도 않는다고 하는데요.

뒤의 코드 예제를 보며 이해해봅시다.

function simpleClosure() {
  let independentVariable = '';
  return function() {
    independentVariable = independentVariable + 'a';
    return independentVariable;
  }
}

위의 예제에서 우리는 simpleClosure라는 함수를 만들었습니다. simpleClosure라는 함수의 리턴 값은 익명함수인데, simpleClosure라는 함수 내부에 있는 independentVariable에 문자열 'a'를 더해 반환하는 익명함수입니다.

브라우저 콘솔에서의 실행 결과는 아래와 같습니다.

simpleClosure를 호출하면 익명 함수가 반환되고, simpleClosure를 호출한 익명 함수를 example이라는 변수에 저장하여 호출하면, "a"이라는 simpleClosure함수 내부의 independentVariable 변수의 값이 나옵니다. 그리고, 그 함수를 한번 더 호출하면 "aa"라는 independentVariable 변수의 값이 나옵니다.

위의 예제의 핵심은 simpleClosure 라는 함수를 이용하여 반환된 익명 함수는 simpleClosure의 내부 변수(independentVariable)를 기억하여 참조하고 있고, 그 내부 변수를 수정하기 위해서는 simpleClosure 함수가 반환한 익명 함수를 이용할 수 있다는 것입니다.

요약하면, simpleClosure가 함수를 반환할 때, 반환되는 익명함수는 simpleClosure의 내부 변수에 대한 참조를 유지한다는 것입니다.

Closure 활용

1. 객체지향 프로그래밍의 Private 변수 따라하기

아래 코드는 closure를 이용하여, counter를 구현한 예제입니다. 클로저는 어휘적 환경을 이용하여 독립적인 공간을 사용할 수 있습니다.

var counter = (function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  };   
})();

console.log(counter.value()); // logs 0
counter.increment();
counter.increment();
console.log(counter.value()); // logs 2
counter.decrement();
console.log(counter.value()); // logs 1

2. Callback 함수에 이용하기

클로저의 반환 값은 '함수'입니다. 그래서 callback함수에 이용할 수 있습니다. 그리고 클로저의 특성상 '독립적인 공간'을 확보한다는 점은 때때로 어떤 문제를 해결할 수 있습니다.

다음은, 입력 폼이 있고, 우리가 입력 폼에 포커스를 줬을 때, '이메일을 입력해주세요.' '아이디를 입력해주세요.' 등의 메세지를 출력하는 예제입니다.

링크 : https://jsfiddle.net/5ogef4mc/4/

위의 화면에서 보다시피, E-mail에 포커싱을 줬는데, 가장 마지막 텍스트인 Your age (you must be over 16)이 표기되고 있습니다. 그 이유는 onfocus로 준 콜백 익명함수가 정확히 해당 시점의 item.help를 기억하는 것이 아니라, 마지막으로 참조된 item.help를 기억하기 때문일 것입니다.

이러한 문제는 var로 선언한 변수를 사용하면서 일어납니다. var는 블록 스코프의 개념을 명확하게 지키지 않아, 변수가 계속 유지되는 특성이 있습니다. 그 유지되는 변수를 참조하면 마지막 item.help를 가리키게 됩니다.

사실 이 문제는 간단하게 var를 let으로 바꾸어주면 해결되지만, 우리는 클로저를 배우러 왔으니, 클로저로 해결해봅시다.

문제 해결의 포인트는 매 onfocus의 콜백을 할당할 때마다, 독립적인 공간을 가진 클로저를 할당해주자 입니다.

위와 같이, 클로저로 독립적인 공간을 할당하고, item 변수에 대한 참조 의존 없이 매 item.help 값 자체를 보관하는 방식으로 해결할 수 있습니다.

결론

간단하게 Closure의 개념과 사용법을 알아보았습니다.

Closure란, 어휘적 환경을 기억하는 함수를 반환하는 함수입니다. Closure를 이용해 할당된 함수들은 독립적인 공간을 갖기 때문에 독립적인 공간을 활용하여 여러가지 일을 할 수 있었습니다.

profile
풀스택 웹개발자로 일하고 있는 Jake Seo입니다. 주로 Jake Seo라는 닉네임을 많이 씁니다. 프론트엔드: Javascript, React 백엔드: Spring Framework에 관심이 있습니다.

0개의 댓글