클로저는 한마디로 요약하면 내부함수가 외부함수에 접근할 수 있는 환경이라고 요약할 수 있다.
자바스크립트 함수는 함수마다 컨텍스트를 가지고 있고 내부에 선언된 함수와 외부 함수는 서로 스코프 체이닝을 통해서 이어져 있다.
var outer = function(){
var a = 1;
var inner = function(){
console.log(a);
}
return inner;
}
var result = outer();
result(); //expect 1
inner함수가 outer함수와 다른 스코프를 가지고 있음에도 a라는 변수를 참조할 수 있는 것은 서로 스코프 체이닝이 이루어져 있기 때문에 가능한 일이다.
클로저는 자바스크립트에서 엄청 중요한 개념 중 하나인데 메모리 누수가 발생할 수 있어 조심히 사용해야 한다고 한다.
var theThing = null;
var replaceThing = function () {
var originalThing = theThing;
var unused = function () {
if (originalThing)
console.log("hi");
};
theThing = {
longStr: new Array(1000000).join('*'),
someMethod: function () {
console.log(someMessage);
}
};
};
setInterval(replaceThing, 1000);
위의 코드를 실행할 경우 메모리 누수가 일어난다.
실제로 실행을 시켜보면 아래와 같이 메모리 누수가 발생하는걸 볼 수 있다.
크롬 V8 엔진은 성능이 좋아서 GC를 돌려 메모리 누수를 막는걸 확인할 수 있다.
반면 파이어폭스는 메모리 누수를 막지 못하여 거의 200MB까지 메모리를 잡아먹는 현상이 발생하는걸 볼 수 있다.
(크롬 1승)
클로저에 의해서 발생하는 문제이다.
위 코드를 설명하면 매 1초마다 replaceThing
을 실행하고, replaceThing
에서는 originalThing
이 기존의 theThing
을 참조 후 , theThing
에는 새로운 객체를 참조한다.
theThing
에는 용량이 큰 longStr
과, someMethod
가 선언되어있고
someMethod
, unused
둘 모두 replaceThing
에 선언되어 있어 같은 상위 스코프를 공유하고있다.
또한 설명하기 앞서 알고 가야하는 잡지식이 있는데
클로저에 의해서 캡쳐링된 지역변수는 자신을 선언한 함수가 끝나있고, 선언된 함수의 내부에 선언되어 있는 함수도 GC 되어있을 경우 GC된다. 라는 점을 알고 가야한다.
현재 위 코드의 문제점은 unused와 someMethod가 같은 스코프를 공유하고 있다는 점이 문제이다.
replaceThing이 호출이 된다 하더라도 현재 someMethod가 전역변수 theThing에 속해있어 스코프를 공유하고있는 someMethod는 GC대상이 아니게 된다.
이 someMethod가 계속 생존해 있어 역시 지역변수 originalThing도 GC대상에서 제외된다.
그 후 replaceThing이 다시 호출되면 새로운 originalThing이 생성되고 theThing을 참조하고 theThing의 someMethod는 GC대상이 아니어서 .... 이런 악순환이 무한으로 반복되어 메모리 누수가 일어나게된다.
레퍼런스 : https://blog.meteor.com/an-interesting-kind-of-javascript-memory-leak-8b47d2e7f156