클로저(Closure)

어떤 함수 A에서 선언한 변수 a를 참조하는 내부함수 B를 외부로 전달할 경우 A의 실행 컨텍스트가 종료된 이후에도 변수 a가 사라지지 않는 현상

  • 자바스크립트에서 클로저에 대한 정의는 함수와 함수를 둘러싼 환경으로 이해할 수 있다.
  • 클로저는 외부함수(내부함수 감싸고 있는)내 스코프 변수에 접근할 수 있고, 그것을 기억하고 있는 내부 함수를 의미
    • 함수를 둘러싼 환경이라는 것이 바로 앞에서 설명했던 렉시컬 스코프이다.
    • 함수를 만들고 그 함수 내부의 코드가 탐색하는 스코프를 함수 생성 당시의 렉시컬 스코프로 고정하면 바로 클로저가 된다.
  • 자바스크립트에서 클로저는 함수가 생성되는 시점에 생성된다.
    • 함수가 생성될 때 그 함수의 렉시컬 환경을 클로저하여 실행될 때 이용
function showName(firstName,lastName) {
    let nameIntro = "Your name is";

      function makeFullName() {
        return makeIntro + firstName + " " + lastName;
    }
  return makeFullName();
}

showName("Michael","Jackson"); // your name is Michael Jackson

Closure는 nodejs의 비동기,논-블로킹 아키텍쳐의 핵심 기능으로 활용된다.

 var base = "Hello, ";
 function sayHelloTo(name) {
     var text = base + name;
       return function() {
        console.log(text);
    }
 }
var hello1 = sayHelloTo('test'); // hello test
var hello2 = sayHelloTo('test2');// hello test1
var hello3 = sayHelloTo('test3'); // hello test3

출력된 결과를 보면 text가 동적으로 변화하고 있는 것처럼 보이지만, 실제로는 text라는 변수 자체가 여러변 생성된 것 이다. 즉, hello(),hello2(),hello3()은 서로 다른 lexical environment를 가지고 있다.

클로저 규칙

  • 클로저는 독립적인 (자유) 변수를 가리키는 함수이다. 또는, 클로저 안에 정의된 함수는 만들어진 환경을 기억한다.
  • 클로저는 외부함수의 스코프 영역에 접근할 수 있고, 그것을 기억하고 있어야 한다.
  • 외부함수가 종료된 후에도 내부함수는 외부함수를 계속 참조하고 있어야 한다.
  • 데이터의 캡슐화 및 정보은닉에도 사용 가능하다.
function celebrityName(firstName) {
    var nameIntro = "This is celebrity is ";
    // 이 내부 함수는 외부함수의 변수와 파라미터에 접근할 수 있습니다.
    function lastName(theLastName) {
        return nameIntro + firstName + " " + theLastName;
    }
    return lastName;
}

// mjName에 클로저를 리턴받습니다.
var mjName = celebrityName("Michael");

// 클로저를 호출시키면 외부함수의 영역을 참조하고 있는 것을 확인할 수 있습니다.
mjName("Jackson"); // This celebrity is Michael Jackson

클로저를 통한 은닉화

자바스크립트에서 객체지향 프로그래밍을 하게되면, Prototype을 통해 객체를 다루는것을 의미한다. ProtoType기반으로 객체지향 프로그래밍을하면, Private variable을 따로 설정할 수 없다는 문제가 있다.

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

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

var hello1 = new Hello('test1');
var hello2 = new Hello('test2');
var hello3 = new Hello('test3');

hello1.say(); // 'Hello, test1'
hello2.say(); // 'Hello, test2'
hello3.say(); // 'Hello, test3'
hello1._name = 'test4'; // javascript naming 규칙에 따라 _name은 private변수로 사용한다고 했지만, 외부에서 접근이 가능하다.
hello1.say(); // 'Hello,test4'

클로저를 이용한다면, oop의 information hiding을 구현할 수 있다. 외부에서 _name변수에 접근할 수 있는 방법이 존재하지 않는다.


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

var hello1 = hello('test1');
var hello2 = hello('test2');
var hello3 = hello('test3');

hello1(); // 'Hello, test1'
hello2(); // 'Hello, test2'
hello3(); // 'Hello, test3'


클로저로 사이드 이펙트 제어

  • 함수에서 값을 반환할 때를 제외하고, 어떤것을 할때 사이드 이펙트가 발생
  • Ajas call이나 time out을 생성하는것도 사이드 이펙트라고 할 수 있다.
function prepareBeverage (order) {
    return function () {
        setTimeout(()=> console.log(`Made a ${order}!`,1000))
    }
}
const takeCoffeeLater = prepareBeverage('coffee');
takeCoffeeLater()

takeCoffLater를 통해서 커피 주문 이라는 클로저를 준비해달라고 요청한다.
takeCoffeeLater를 호출해야 커피를 찾아갈 수 있다.

클로저의 성능

  • 클로저는 각자의 환경을 가지게 된다. 즉, 메모리에 할당이 된다.

  • 클로저를 생성해 놓고 참조를 제거하지 않는것은 메모리 낭비가 된다.

  • 클로저를 통해 내부 변수를 참조하는 동안에는 내부 변수가 차지하는 메모리를 GC가 회수하지 않는다.

  • 메모리 관리라는 약점이 존재하지만, 스코프 체인을 탐색하는 시간과 새로운 스코프를 생성하는 비용을 비교했을때 결코 나쁘지만은 않다.

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

var hello1 = hello('test1');
var hello2 = hello('test2');
var hello3 = hello('test3');

hello1(); // 'Hello, test1'
hello2(); // 'Hello, test2'
hello3(); // 'Hello, test3'

hello1 = null;
hello2 = null;
hello3 = null;