Notice
Recent Posts
Recent Comments
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
Archives
Today
Total
관리 메뉴

코린이 탈출기

[JavaScript] 클로저(Closure) 본문

FE 공부

[JavaScript] 클로저(Closure)

명란파스타 2020. 11. 9. 16:37

클로저 개념

클로저는 내부함수가 외부함수의 맥락(Context)에 접근할 수 있는 것을 가리킵니다.

클로저는 자바스크립트를 이용한 고난도 테크닉을 구사하는 데에 필수적인 개념으로 활용되므로 중요합니다.

 

흔히, 함수 내에서 함수를 정의하고 사용하면 클로저라고 합니다.

클로저 안에 정의된 함수는 만들어진 환경을 기억한다는 특징이 있습니다.

 

var base = 'Hello, ';
function sayHelloTo(name) {
  var text = base + name;
  return function() {
    console.log(text);
  };
}

var hello1 = sayHelloTo('A');
var hello2 = sayHelloTo('B');
var hello3 = sayHelloTo('C');
hello1(); // 'Hello, A'
hello2(); // 'Hello, B'
hello3(); // 'Hello, C'

출력된 결과를 보면 text 변수가 동적으로 변하고 있는 것처럼 보입니다.

하지만, 실제로는 text라는 변수 자체가 여러번 생성된 것입니다.

즉 hello1(), hello2(), hello3()는 서로 다른 환경을 가지고 있습니다.

 

클로저를 통한 은닉화

일반적으로 JavaScript에서 객체지향 프로그래밍을 말한다면, Prototype을 통해 객체를 다루는 것을 말합니다.

prototype을 통한 객체를 만들 때 중요한 문제 중 하나는 Private Variables에 대한 접근 권한 문제입니다.

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

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

var hello1 = new Hello('A');
var hello2 = new Hello('B');
var hello3 = new Hello('C');

hello1.say(); // 'Hello, A'
hello2.say(); // 'Hello, B'
hello3.say(); // 'Hello, C'
hello1._name = 'anonymous';
hello1.say(); // 'Hello, anonymous'

hello1._name으로 외부에서 객체에 대해서 쉽게 접근할 수 있고, 이를 통한 값 변경도 가능합니다.

이 경우, 클로저를 통해 외부에서 변수에 직접 접근하는 것을 제한할 수 있습니다.

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

var hello1 = hello('A');
var hello2 = hello('B');
var hello3 = hello('C');

hello1(); // 'Hello, A'
hello2(); // 'Hello, B'
hello3(); // 'Hello, C'

이 방식을 사용하면 특별히 인터페이스를 제공하는 것이 아니라면, 외부에서 _name에 접근할 방법이 전혀 없다.

 

반복문 클로저

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

간단하게 0-9까지의 정수를 출력하는 코드이지만, 실제로 돌려보면 엉뚱하게도 10만 열 번 출력되는 것을 알 수 있습니다.

먼저 setTimeout()에 인자로 넘긴 익명함수는 모두 0.1초 뒤에 호출되는데, 그 0.1초 동안에 이미 반복문이 모두 순회되어 i값은 이미 10이 된 상태이고 그 때 익명함수가 호출되어서 이미 10이 된 i를 참조하게 되는 것입니다.

이 경우에도 클로저를 사용하면 원하는대로 동작하도록 할 수 있습니다.

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

중간에 IIFE(Immediately Invoked Function Expression, 즉시 실행되는 함수 표현식)을 덧붙여 setTimeout()에 걸린 익명함수를 클로저로 만듭니다.

클로저는 만들어진 환경을 기억합니다.

이 코드에서 i는 IIFE내에 j라는 형태로 주입되고, 클로저에 의해 각각 다른 환경 속에 포함됩니다.

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

 

만약, 인자로 i를 넘기지 않는다면 당연히 클로저가 참조하는 IIFE의 함수 스코프에서도 i값이 없으므로 생성당시의 외부슼코프인 글로벌을 탐색하게 되고, 결국 모두 같은 i를 참조하게 됩니다.

반면에 i값을 넘기게 되면, IIFE로 만든 10개의 스코프에 모두 i라는 변수가 다른 값이므로 정상적으로 작동합니다.

 

클로저 성능

클로저는 각자의 환경을 가집니다.

이 환경을 기억하기 위해서는, 당연히 메모리가 소모될 것입니다.

클로저를 통해 내부 변수를 참조하는 동안에는 내부변수가 차지하는 메모리가 GC가 회수하지 않기 때문에, 클로저 사용이 끝나면 참조를 제거하는 것이 좋습니다.

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

var hello1 = hello('A');
var hello2 = hello('B');
var hello3 = hello('C');

hello1(); // 'Hello, A'
hello2(); // 'Hello, B'
hello3(); // 'Hello, C'

// 메모리를 release 시키기 -> 클로저의 참조 해제
hello1 = null;
hello2 = null;
hello3 = null;