2016년 1월 28일 목요일

JavaScript Closure 란?

1) CloSure란?
 - 로컬 변수를 참조하고 있는 함수 내의 함수
 - 즉, 자신의 범위 밖에 있는 변수들에 접근할 수 있는 함수를 의미한다.
 - inner function을 return 할때 closure가 된다.

function outerFn() {
var count = 1;
return function(cnt) {
count += cnt;
console.log(count);
}
}

var func = outerFn();
func(10); // 결과 값 11
func(10); // 결과 값 21
1. func가 closure가 됨 : 내부 변수들 closure, private 변수 생성됨
2. func(parameter) 호출해 줘도 내부 변수들은 다시 생성되는 것이 아니라 상태를    유지시켜서 참조 됨
3. 즉, 클로저가 만들어 지면서 내부 변수들은 별도로 유지되면서 상태값을 유지함
4. 클로저의 참조를 제거하고 GC할려면 명시적으로 func=null; 함


다른 예제.
예) closure가 아님

function foo(x) {

var tmp = 3;

function bar(y) {

console.log(x + y + (++tmp));

}

bar(10);

}

foo(2); // 결과 값 16

foo(2); // 결과 값 16

foo(2); // 결과 값 16예) closure 임function foo(x) {

var tmp = 3;

return function(y) {

console.log(x + y + (++tmp));

}

}

var bar = foo(2); // bar is now a closure.

bar(10); // 결과 값 16



- 로컬 변수를 계속 생성하지 않고 상태를 유지하면 사용할 수 있다 (마치 객체처럼     var bar2 = foo(2); 하면 또 다른 closure가 생성되면서 bar와 별도의 상태공유   변수 x와 tmp가 생성된다. 마치 클래스에서 객체생성하는 것 처럼 된다. closure   생성시에 arguement와 var로 선언된 variable 에 대한 참조가 가능해 진다. 결국   참조자들-x, tmp-가 일정 메모리에 저장되어 상태를 유지한다)

- 위의 예에서 var bar = foo(2);를 하는 순간 foo function 객체의 x, tmp 변수의   레퍼런스를 closure 가 가진다 (즉, x, tmp 에 대한 변수 상태값이 유지되고, y     값은 bar(10)을 호출할 때 넘겨주는 값이 된다)

- 클로저는 간단한 객체이다 (엄밀히 따져서 객체는 아니지만 객체식으로 치환해서 살   펴보면 다음과 같다)
    + 클로저 : 객체 = 클로저를 감싸고 있는 부모 함수 foo : 생성자
    + 클로저 : 객체 = 클로저로 부터 참조되는 로컬 변수 tmp :  프로퍼티
    + 클로저 : 객체 = 클로저 자신 bar, bar2 : 메소드
    + 클로저 : 객체 = var bar = foo(2) 함수 호출 : 인스턴스화
    + 클로저 : 객체 = 클로저를 대입하는 변수 bar(10), bar2(10) : 인스턴스 <-- 요거 애매함

2) 클로져를 이용해서 private 함수 흉내내기


몇몇 언어(예를들어 자바)는 같은 클래스 내부의 메소드에서만 호출할 수 있는 private 메소드를 지원한다.



자바스크립트는 이를 지원하지 않지만 클로져를 이용해서 흉내낼 수 있다. private 함수는 코드에 제한적인 접근만을 허용한다는 점 뿐만 아니라 전역 네임스페이스를 깔끔하게 유지할 수 있다는 점에서 중요하다.



아래에 모듈 패턴이라고 알려진 클로져를 통해 몇 개의 public 함수가 private 함수와 변수에 접근하는 코드가 있다.
var Counter = (function() {
var privateCounter = 0;

function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
}
})();
alert(Counter.value()); /* 0 */
Counter.increment();
Counter.increment();
alert(Counter.value()); /* 2 */
Counter.decrement();
alert(Counter.value()); /* 1 */


이전 예제에서는 각 클로져가 자기만의 환경을 가졌지만 이 예제에서는 하나의 환경을 Counter.increment, Counter.decrement, Counter.value 세 함수가 공유한다.



공유되는 환경은 정의되자마자 실행되는 익명 함수 안에서 만들어진다. 이 환경에는 두 개의 private 아이템이 존재한다. 하나는 privateCounter라는 변수이고 나머지 하나는 changeBy라는 함수이다. 이 두 아이템 모두 익명함수 외부에선 접근할 수 없다. 하지만 익명함수 안에 정의된 세개의 public 함수에서 사용되고 반환된다.



이 세개의 public 함수는 같은 환경을 공유하는 클로져이다. 자바스크립트 어휘 스코핑(lexical scoping) 덕분에 세 함수 모두 privateCounter 변수와 changeBy 함수에 접근할 수 있다.



익명 함수가 카운터를 정의하고 이것을 Counter 변수에 할당한다는 걸 알아차렸을 것이다. 이 함수를 다른 변수에 저장하고 이 변수를 이용해 여러개의 카운터를 만들수도 있다.
var makeCounter = function() {
var privateCounter = 0;

function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
}
};
var Counter1 = makeCounter();
var Counter2 = makeCounter();
alert(Counter1.value()); /* 0 */
Counter1.increment();
Counter1.increment();
alert(Counter1.value()); /* 2 */
Counter1.decrement();
alert(Counter1.value()); /* 1 */
alert(Counter2.value()); /* 0 */

두개의 카운터가 어떻게 독립적으로 존재하는지 주목하라. makeCounter() 함수를 호출하면서 생긴 환경은 호출할 때마다 다르다. 클로져 변수 privateCounter 는 다른 인스턴스를 가진다.


객체지향 프로그래밍을 사용할 때 얻는 이점인 정보 은닉과 캡슐화를 클로져를 사용함으로써 얻을 수 있다.



3) 자주하는 실수: 반복문 안에서 클로져 만들기
helpText 배열은 세개의 도움말을 정의한다. 각 도움말은 입력 필드의 ID와 연관된다. 이 세개의 정의를 반복하며 입력필드에 onfocus 이벤트가 발생했을 때 입력필드에 해당하는 도움말을 표시한다.

이 코드를 실행해보면 제대로 동작하지 않는다는 것을 알 수 있다. 어떤 필드에 포커스를 주더라도 나이에 관한 도움말이 표시된다.

이유는 onfocus 이벤트에 지정한 함수가 클로져라는 것이다. 이 클로져는 함수 본체와 setupHelp 함수의 스코프로 이루어져 있다. 세개의 클로져가 만들어졌지만 각 클로져는 하나의 환경을 공유한다. 반복문이 끝나고 onfocus 콜백이 실행될 때 콜백의 환경에서 item 변수는 (세개의 클로져가 공유한다) helpText 리스트의 마지막 요소를 가리키고 있을 것이다.

여러개의 클로져를 이용해서 문제를 해결할 수 있다. 위에서 언급한 함수 공장을 사용해보자.
예상한대로 작동한다. 콜백이 하나의 환경을 공유하지 않고 makeHelpCallback 함수가 만든 새로운 환경을 가진다. 이 환경에는 helpText 배열로부터 해당하는 문자열이 help 변수에 담겨있다.

4) 성능과 관련해서



클로져가 필요하지 않은 작업인데도 함수안에 함수를 만드는 것은 스크립트 처리 속도와 메모리 사용량 모두에서 현명한 선택이 아니다.

예를들어 새로운 오브젝트나 클래스를 만들 때 오브젝트 생성자에 메쏘드를 정의하는 것 보다 오브젝트의 프로토타입에 정의하는것이 좋다. 오브젝트 생성자에 정의하게 되면 생성자가 불릴때마다 메쏘드가 새로 할당되기 때문이다.


비현실적이지만 설명을 위해 예제를 첨부했다.
function MyObject(name, message) {
this.name = name.toString();
this.message = message.toString();
this.getName = function() {
return this.name;
};
this.getMessage = function() {
return this.message;
};
}


위의 코드는 일일히 메쏘드를 만들면서 클로져의 이점을 살리지 못하고 있다.
function MyObject(name, message) {
this.name = name.toString();
this.message = message.toString();
}
MyObject.prototype = {
getName: function() {
return this.name;
},
getMessage: function() {
return this.message;
}
};


또는 다음처럼 하자
function MyObject(name, message) {
this.name = name.toString();
this.message = message.toString();
}
MyObject.prototype.getName = function() {
return this.name;
};
MyObject.prototype.getMessage = function() {
return this.message;
};



* reference 
  • http://mobicon.tistory.com/48
  • https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Closures
  • http://blog.jui.io/?p=25
Share:

0 개의 댓글:

댓글 쓰기