JavaScript 클로저에서 메모리 누수 방지하는 법
JavaScript 클로저는 클로저 함수에 대한 참조가 있는 한 메모리에서 객체를 계속 유지합니다. 객체가 더 이상 필요 없는데도 클로저에 캡처되어 있어 가비지 컬렉터가 이를 해제하지 못하면 누수가 발생합니다.
대형 객체가 포함된 전형적인 사례를 보죠:
let bigData = {
id: 42,
data: new Array(1000000).fill('Some data')
}
function handler(data) {
return function() {
console.log(data.id);
debugger;
}
}
const runHandler = handler(bigData);
bigData = null;
runHandler();
내부 함수는 data.id만 사용하지만, 클로저는 전체 bigData 객체를 캡처합니다. DevTools에서 브레이크포인트에서 객체가 여전히 메모리에 남아 있는 걸 확인할 수 있습니다.
클로저 내 eval()의 문제점
eval() 함수는 사용 여부와 상관없이 외부 변수 전체를 클로저에 추가해 누수를 악화시킵니다:
let hello = 'Hello';
let userName = 'Vasya';
setTimeout(function () {
debugger;
return eval('console.log("Hello from Vasya!")');
}, 1000);
DevTools에서 클로저에 hello와 userName이 포함되어 있는 걸 볼 수 있습니다. eval() 내부 코드가 불확실하기 때문입니다. 프로덕션에서는 eval()을 피하세요.
누수 방지 방법
캡처 데이터 강제 초기화
사용 후 캡처된 데이터를 초기화하세요:
function runCalc(buttonId) {
let bigData = new Array(1000000).fill('Something');
const button = document.getElementById(buttonId);
button.addEventListener('click', function onClick() {
console.log('Processed items:', bigData.length);
bigData = null;
});
}
null 할당으로 클로저 내 참조를 해제해 가비지 컬렉터가 객체를 제거할 수 있게 합니다.
이벤트 핸들러 제대로 제거하기
DOM 요소 제거 시 이벤트 핸들러가 흔한 누수 원인입니다:
function clickSetup(btn, message) {
const bigData = new Array(1000000).fill(message);
function handler() {
console.log(message, bigData.length);
btn.remove();
btn.removeEventListener('click', handler);
}
btn.addEventListener('click', handler);
}
이름이 붙은 함수 handler는 요소와 함께 제거할 수 있습니다. 익명 화살표 함수는 이렇게 제거할 수 없습니다.
약한 참조를 위한 WeakMap
WeakMap은 객체 키에 대한 약한 참조를 사용해 대형 객체 캡처 문제를 해결합니다.
키는 반드시 객체여야 함
const weak = new WeakMap();
const key = {};
weak.set(key, 'Vasya was here!');
console.log(weak.get(key));
const anotherKey = {};
console.log(weak.get(anotherKey)); // undefined
키는 객체 또는 비원시형이어야 합니다. 참조로 get(key) 접근.
약한 참조와 가비지 컬렉션
WeakMap의 참조는 가비지 컬렉션을 막지 않습니다:
const weak = new WeakMap();
let userKey1 = {id: 1};
weak.set(userKey1, 'Vasya');
userKey1 = null;
userKey1을 null로 만든 후 객체와 값이 가비지 컬렉트됩니다.
반복자 없음
size, forEach, keys() 없음. 기본 메서드만:
set(key, value)— 설정get(key)— 가져오기has(key)— 확인delete(key)— 삭제
가비지 컬렉션 중 예측 불가능성을 피하기 위해 반복자 없음.
클로저에서 WeakMap 사용
WeakMap이 포함된 클로저는 객체를 강하게 유지하지 않습니다:
function createProcessor() {
const weakMap = new WeakMap();
return function process(obj) {
weakMap.set(obj, obj.value);
debugger;
return weakMap.get(obj);
}
}
const processor = createProcessor();
let bigData = {
value: 21,
hugeArray: new Array(1000000).fill('*')
};
console.log(processor(bigData));
bigData = null;
let bigData2 = {
value: 42,
hugeArray: new Array(1000000).fill('1')
};
console.log(processor(bigData2));
DevTools에서 가비지 컬렉션 후 bigData2만 남아 있습니다. WeakMap으로 자동 메모리 정리 가능.
확인 방법:
process()에 브레이크포인트 설정.- 코드 단계별 실행 (F10).
bigData = null후 Memory 탭으로 이동.- 버튼으로 가비지 컬렉션 트리거.
- 계속 — 현재 키만 남음.
주요 요점
- 클로저는 한 속성만 사용해도 전체 객체를 캡처.
eval()은 외부 변수 전체를 클로저에 추가.- 이름 붙은 이벤트 핸들러는
removeEventListener로 제거 가능. WeakMap은 객체 키에 약한 참조 사용.WeakMap에 반복자 없어 가비지 컬렉션 예측 가능.
— Editorial Team
아직 댓글이 없습니다.