일반적으로 memory leak은 말그대로 메모리 누수입니다. 참조되는 곳이 없는 객체는 해당 객체에 접근할 수 있는 방법이 없고, 사용될 일이 없기 때문에 메모리에서 삭제 됩니다.
JavaScript의 GC(Garbage Collection)가 주기적으로 돌면서 이 역할을 수행합니다. 헌데 사용하지 않는 객체임에도 불구하고 특정 변수가 이를 참조(reference)하고 있어서 메모리에서 삭제되지 않는 현상을 memory leak이라고 합니다.

보통은 크게 신경쓸 필요가 없습니다. 브라우져가 새로고침 될때마다 메모리 릴리즈 되기 때문이죠. 하지만 SPA(Single Page Application)라면 얘기는 달라집니다. 메모리 누수가 발생한다면 페이지를 이동할때마다 메모리 사용량이 늘어날 것이고 이는 어플리케이션 성능 저하로 이어집니다.

이번 포스트에서 memory leak의 주제는 DOM leak입니다. 말그대로 DOM 객체가 제대로 메모리에서 삭제되지 않아 발생하는 현상입니다.

보통 document에서 element가 삭제되면 이는 GC에 의해 메모리에서 제거됩니다. 예를 보면..

1
<div id="area"></div>

1
2
3
4
5
6
7
8
9
10
11
12
function addElement() {
const div = document.createElement('DIV');
div.id = 'out_box';
div.innerHTML = '<div id="in_box">추가된 엘리먼트 입니다.';
document.getElementById('area').appendChild(div);
}
function removeElement(){
document.getElementById('area').removeChild(document.getElementById('out_box'));
}
addElement(); //DOM에 엘리먼트 추가
removeElement(); //DOM에서 엘리먼트 삭제

out_box라는 아이디를 가진 div엘리먼트를 추가하고 removeChild 메소드를 써서 바로 삭제한 것입니다. 이 경우 삭제한 엘리먼트(out_box)는 더이상 참조 되고 있지 않으므로 GC에 의해 메모리에서 제거 됩니다.
그렇다면 다음 의 경우에는 어떨까요?

1
2
3
4
5
6
7
8
9
10
11
function addElement() {
const div = document.createElement('DIV');
div.id = 'out_box';
div.innerHTML = '<div id="in_box">추가된 엘리먼트 입니다.';
document.getElementById('area').appendChild(div);
}
function removeElement(){
window.inBox = document.getElementById('in_box');//바뀐 부분
document.getElementById('area').removeChild(document.getElementById('out_box'));
}

다른 내용은 모두 똑같고, div#in_box를 참조하는 inBox라는 변수를 전역에 만든것만 바뀌었습니다. 이렇게 하면 document DOM에서 해당 div#out_box이 삭제되었지만 메모리 상에는 남아 있게 됩니다.
구글 개발자도구의 profile을 이용해 이를 확인해보겠습니다.

addElement실행 전과 후를 비교해 보면…

엘리먼트가 추가되면서 DOM 엔트리가 22개에서 25개로 늘었습니다.

addElement실행 후와 removeElement 실행 후를 비교해 보면…

엘리먼트가 제거되면서 DOM 엔트리가 25개에서 22개로 다시 줄었습니다. 그리고 Detached DOM tree가 생성 되었습니다. 즉, document에서 분리된 DOM tree가 생성되었고, 이것을 inBox변수가 참조하고 있어서 GC가 이를 수집하지 않은 것입니다.

눈치채셨을수도 있지만, removeChild는 인자로 넘기는 엘리먼트를 DOM tree에서 분리하는 역할만을 수행합니다. 메모리 제거와는 관계가 없죠. 그리고 분리된 DOM tree를 리턴합니다. 따라서 이를 변수에 받아 다른 위치에 appendChild하는 등의 작업을 수 행할 수 있습니다. 반대로 이를 변수에 참조시키지 않고, 분리된 DOM tree의 특정 엘리먼트를 참조하고 있는 곳이 없다면 GC가 이를 수집하여 메모리에서 제거하게 됩니다.