Closure

클로저란 자바스크립트에 있어서 C언어의 포인터와 같은 관점으로 볼 수 있습니다. 먼저 scope chain에 대해 알아야 합니다.

scope chain

scope는 비동기 함수가 호출될 때까지 계속해서 지속되어 참고가 됩니다.(scope의 지속성) 새로운 scope를 생성함으로써 비동기적으로 호출 될 때의 scope를 조율할 수 있습니다.

아래의 예제에서는 outer() 함수 호출 후 inner() 내에 변수 a 에 x 를 대입할 때 실행문을 포함하고 있는 함수의 변수 스코프부터 먼저 검색됩니다. 만약 없는 경우 상위 스코프로 검색을 시작하게 되며 전역 스코프에도 없는 경우 에러를 발생시킵니다.

var x = 1;
function outer() {
  var y = 2;
  function inner() {
    var a = x;
  }
}
 
outer();

Clusure

클로저란 함수가 함수 내부에 선언된 변수에 접근할 수 있는 함수를 말합니다.

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

아래의 예제를 통해 알아보겠습니다.

function makeFunc() {
  var name = "Mozilla";
  function displayName() {
    alert(name);
  }
  return displayName;
}
 
var myFunc = makeFunc();
myFunc();

위의 예제에서 name 변수는 makeFunc라는 함수 스코프에 존재합니다. 즉 함수 외부에서는 접근을 할 수 없습니다. 하지만 displayName라는 함수 내부의 함수를 name가 name 변수를 참조하게 하여 함수 외부에서도 접근할 수 있도록 return 을 하여 myFunc() 함수에서도 name을 접근할 수 있습니다.

생성 시 환경을 기억하는 클로저

클로저는 만들어질 때의 환경을 기억합니다. 다음 예제를 살펴보도록 하겠습니다.

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

위 예제는 0~9까지의 출력을 예상할 수 있지만 setTimeout 함수에 의해 0.1초를 기다리는 동안 i가 10번을 돌게 되어 익명 함수 호출시에는 i가 이미 10이 되어 10을 10번 출력하게 됩니다.

이 상황을 클로저를 이용하여 생성 당시의 환경(i의 값)을 기억하도록 하여 setTimeout 함수에 의해 0.1초 후 익명함수가 호출되더라도 기억하고 있는 i의 값을 출력합니다.

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

객체와 클로저를 이용한 데이터 은닉

일반적으로 객체를 정의할 때 prototype을 이용하지만 이 방법을 사용하게 되면 외부로부터 데이터를 보호할 수 없게 됩니다.

function Hello(name) {
  this._name = name;
}
 
Hello.prototype.say = function() {
  console.log('Hello, ' + this._name);
}
 
var hello = new Hello("person");
 
hello._name = "person2";

이 경우 클로저를 이용하여 외부로부터 데이터를 보호할 수 있습니다.

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

Object VS Closure

먼저 객체 생성 시 객체의 프로토타입을 연결하는 방식이 더 좋다. 그 이유는 Object를 사용하게되면 생성자가 호출 될 때마다 메서드가 다시 할당된다.

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
  this.getName = function() {
    return this.name;
  };
 
  this.getMessage = function() {
    return this.message;
  };
}

클로저를 이용하게 되면 이 부분을 보완할 수 있으며 prototype에 다시 정의하는 방법보다 기존 프로토타입에 정의를 하는 것이 좋습니다.

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
 
/*
MyObject.prototype = {
  getName: function() {
    return this.name;
  },
  getMessage: function() {
    return this.message;
  }
};
*/
 
MyObject.prototype.getName = function() {
  return this.name;
};
MyObject.prototype.getMessage = function() {
  return this.message;
};

위 코드에서 즉시 함수 실행 기법 을 사용하면 더 좋은 성능을 얻을 수 있습니다. (함수 표현은 함수 정의 및 저장 후 실행하는 과정을 거치지만 즉시 실행함수를 사용하게 되면 함수 정의 후 바로 실행하여 위의 과정을 거치지 않습니다.)

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
 
(function() {
    this.getName = function() {
        return this.name;
    };
    this.getMessage = function() {
        return this.message;
    };
}).call(MyObject.prototype);

클로저 사용 후 정리하기

c 와 c++ 에서 동적할당 후 마지막에 동적해제하는 과정을 거칩니다. 그 이유는 메모리를 회수하기 위함입니다. 자바스크립트에서는 스코프 체인 성질에 의해 어떠한 변수 혹은 함수가 참조를 하고 있으면 내부 변수가 차지하는 메모리를 GC(Garbage Collector)가 회수하지 않습니다. 따라서, 클로저 사용 후 참조를 제거하는 것이 좋습니다.

hello = null;


+ Recent posts