클로저(Closure)

EJ·2021년 1월 6일
2

기술 공부

목록 보기
3/18

클로저란?

클로저란 독립적인 (자유)변수를 가리키는 함수이다. 클로저 안에 정의된 함수는 만들어진 환경을 기억한다.

쉽게 말해, 함수 내에서 함수를 정의하고 사용하면 클로저이다. 하지만 대부분 정의한 함수를 리턴하고 사용은 바깥에서 하게 된다.

function getClosure() {
  var text = 'variable 1';
  return function() {
    return text;
  };
}

var closure = getClosure();
console.log(closure()); // 'variable 1'

위에서 정의한 getClosure()는 함수를 반환하고, 반환된 함수는 getClosure() 내부에서 선언된 변수인 text를 참조하고 있다. 이렇게 참조된 변수는 함수 실행이 끝났다고 해서 사라지지 않고, 여전히 제대로 된 값을 반환하게 된다.

이 반환함수가 클로저인 것이다.


클로저를 통한 은닉화

일반적으로 JavaScript에서 객체지향 프로그래밍은 Prototype을 통해 객체를 다루는 것을 말한다. Proptotype을 통한 객체를 만들 때 중요한 문제 중 하나는 Private variables에 대한 접근 권한 문제이다.

Prototype
자바스크립트에는 객체지향 언어이지만, 클래스(class)라는 개념이 존재하지 않는다. 대신 프로토타입(Prototype)이라는 것이 존재하는 것이다.
(최근 ECMA6 표준에서는 Class문법이 추가되었다 . 하지만 문법이 추가되었을 뿐, 자바스크립트가 클래스 기반으로 바뀌었다는 것은 아니다.)

클래스가 없기 때문에 기본적으로 상속기능도 없다. 그래서 보통 프로토타입을 기반으로 상속을 흉내내도록 구현해 사용한다.

( 해당 블로그 참고 )

function Hello(name) {
  this._name = name;
}

Hello.prototype.say = function() {
  console.log('Hello, ' + this._name);
}

var hello1 = new Hello('승민');
var hello2 = new Hello('현섭');
var hello3 = new Hello('유근');

hello1.say(); // 'Hello, 승민'
hello2.say(); // 'Hello, 현섭'
hello3.say(); // 'Hello, 유근'
hello1._name = 'anonymous';
hello1.say(); // 'Hello, anonymous'

위에서 Hello()로 생성된 객체는 모두 _name이라는 변수를 가지게 된다. 변수명 앞에 underScore(_)를 포함했기 때문에 일반적인 JavaScript 네이밍 컨벤션을 생각해 봤을때 이 변수는 Private variable로 쓰고싶다는 의도를 알 수 있다. 하지만 여전히 외부에서도 변수에 쉽게 접근할 수 있는 상태이다.

따라서, 클로저를 사용하면 외부에서 변수에 직접 접근하는 것을 제한할 수 있다.

function hello(name) {
  var _name = name;
  return function() {
    console.log('Hello, ' + _name);
  };
}

var hello1 = hello('승민');
var hello2 = hello('현섭');
var hello3 = hello('유근');

hello1(); // 'Hello, 승민'
hello2(); // 'Hello, 현섭'
hello3(); // 'Hello, 유근'

이렇게 작성하면 외부에서 _name에 접근할 방법이 없어지는 것이다.


반복문 클로저

var i;
for (i = 0; i < 10; i++) {
  setTimeout(function() {
    console.log(i);
  }, 100);
}

위 코드는 10만 열 번 출력하는 결과를 가져온다. 왜일까?

setTimeout()에 인자로 넘긴 익명함수는 0.1초 뒤에 호출된다. 0.1초 동안에 이미 for문이 순회되면서 i의 값이 10이 되어버리는 것이다. 따라서 익명함수가 호출되었을 때는 이미 10이 되어버린 i를 참조하게 되는 것이다.

이런 경우에도 클로저를 사용해 원하는대로 동작하게 만들 수 있다.

var i;
for (i = 0; i < 10; i++) {
  (function(j) {
    setTimeout(function() {
      console.log(j);
    }, 100);
  })(i);
}

위 코드는 중간에 IIFE(즉시 실행 함수 표현)을 덧붙여 setTimeout()에 걸린 익명함수를 클로저로 만든 것이다.

i는 IIFE내에 j라는 형태로 주입되고, 클로저에 의해 각기 다른 환경속에 포함되게 된다.

반복문은 10회 반복이므로 10개의 환경이 생길 것이고, 10개의 서로 다른 환경에 10개의 서로 다른 j가 생성되는 것이다.

이 부분 이론상으로는 이해가 되는데 코드를 보면 이해가 잘 되지 않는다.


클로저의 성능

클로저는 각자의 환경을 가지고, 이 환경을 기억하기 위해서는 메모리가 소모된다. 따라서 클로저 사용이 끝나면 참조를 제거하는 것이 좋다.

function hello(name) {
  var _name = name;
  return function() {
    console.log('Hello, ' + _name);
  };
}

var hello1 = hello('승민');
var hello2 = hello('현섭');
var hello3 = hello('유근');

hello1(); // 'Hello, 승민'
hello2(); // 'Hello, 현섭'
hello3(); // 'Hello, 유근'

// 여기서 메모리를 release 시키기 클로저의 참조를 제거해야 한다.
hello1 = null;
hello2 = null;
hello3 = null;

💡 요약
자바스크립트는 함수 안에서 또다른 함수를 선언할 수 있다.

클로저란 내부함수가 외부함수의 맥락에 접근할 수 있는 것이다. 즉, 내부함수는 외부함수의 지역변수에 접근할 수 있는데, 외부함수가 소멸된 이후에도 내부함수가 외부함수의 변수에 접근할 수 있는 것이다.

외부함수의 지역변수를 사용하는 내부함수가 소멸될 때까지 외부함수는 소멸되지 않는다고 보면 된다.

이처럼, 클로저는 각자의 환경을 기억하기 위해서 메모리가 소모된다. 따라서, 클로저 사용이 끝나면 참조를 제거하는 것이 좋다.

클로저는 정보의 은닉화, 캡슐화를 위해 주로 사용된다.
클로저는 함수를 구성하는 코드와 함수가 생성될 당시의 스코프 환경(Lexical Environment)으로 구성된다. 따라서, 함수가 생성될 당시의 모든 변수를 기억해 두었다가 함수가 호출될 때 사용할 수 있는 것이다. 또한, 해당 스코프 내에서 선언된 변수에 대해 해당 스코프 범위 바깥에서는 접근할 수 없기 때문에 캡슐화가 가능해진다.


클로저는 폐쇄적인 스코프에서 외부로 정보를 제공할 수 있는 유일한 수단이다. 클로저함수 내에서 리턴해줘야한다.

접근 권한 제어
지역 변수 보호
데이터 보존 및 활용

profile
주니어 프론트엔드 개발자 👼🏻

0개의 댓글