그럼에도 불구하고

👨‍💻

[JavaScript] DOMContentLoaded, load의 차이 본문

JavaScript/JavaScript basics

[JavaScript] DOMContentLoaded, load의 차이

zenghyun 2023. 3. 22. 21:56

오늘은 DOMContentLoaded과 load의 차이점에 대해 알아보겠습니다. 

 

 

[ HTML 생명주기 ]

HTML 문서의 생명주기에는 다음과 같은 3가지 주요 이벤트가 관여합니다. 

  • DOMContentLoaded – 브라우저가 HTML을 전부 읽고 DOM 트리를 완성하는 즉시 발생합니다. 이미지 파일(<img>)이나 스타일시트 등의 기타 자원은 기다리지 않습니다.
  • load – HTML로 DOM 트리를 만드는 게 완성되었을 뿐만 아니라 이미지, 스타일시트 같은 외부 자원도 모두 불러오는 것이 끝났을 때 발생합니다.
  • beforeunload/unload – 사용자가 페이지를 떠날 때 발생합니다.

세 이벤트는 다음과 같은 상황에서 활용할 수 있습니다.

  • DOMContentLoaded – DOM이 준비된 것을 확인한 후 원하는 DOM 노드를 찾아 핸들러를 등록해 인터페이스를 초기화할 때
  • load – 이미지 사이즈를 확인할 때 등. 외부 자원이 로드된 후이기 때문에 스타일이 적용된 상태이므로 화면에 뿌려지는 요소의 실제 크기를 확인할 수 있음
  • beforeunload – 사용자가 사이트를 떠나려 할 때, 변경되지 않은 사항들을 저장했는지 확인시켜 줄 때
  • unload – 사용자가 진짜 떠나기 전에 사용자 분석 정보를 담은 통계자료를 전송하고자 할 때

이제 각 이벤트에 대하여 자세히 살펴보도록 하겠습니다. 

 

[ DOMContentLoaded ]

DOMContentLoaded 이벤트는 document 객체에서 발생합니다.

따라서 이벤트를 다루려면 addEventListener 사용해야 합니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
>script>
    function getImg() {
        alert('DOM이 준비되었습니다.');
 
        // 이미지가 로드되지 않은 상태이기 때문에 사이즈는 0X0 입니다.
        alert(`이미지 사이즈: ${img.offsetWidth}x${img.offsetHeight}`);
}
 
document.addEventListener('DOMContentedLoaded', getImg);
 
</script>
 
<img id="img" src="https//example.com> 
cs

 

위 예시에서 DOMContentLoaded 핸들러는 문서가 로드되었을 때 실행됩니다. 따라서 핸들러 아래쪽에 위치한 <img>뿐만 아니라 모든 요소에 접근할 수 있습니다.

그렇지만 이미지가 로드되는 것은 기다리지 않기 때문에 alert 창엔 이미지 사이즈가 0이라고 뜹니다.

 

처음 DOMContentLoaded 이벤트를 접하면 "DOM 트리가 완성되면 DOMContentLoaded 이벤트가 발생한다"라고 생각하기 때문에 그다지 복잡하지 않은 이벤트라는 생각이 들기도 합니다.  그런데 DOMContentLoaded에는 몇 가지 특이사항이 있습니다.

 

[ DOMContentLoaded와 scripts ]

브라우저는 HTML 문서를 처리하는 도중에 <script> 태그를 만나면, DOM 트리 구성을 멈추고 <script>를 실행합니다. 스크립트 실행이 끝난 후에야 나머지 HTML 문서를 처리합니다. <script>에 있는 스크립트가 DOM 조작 관련 로직을 담고 있을 수 있기 때문에 이런 방지책이 만들어졌습니다. 따라서 DOMContentLoaded 이벤트 역시 <script> 안에 있는 스크립트가 처리되고 난 후에 발생합니다.

 

예시를 통해 이를 살펴봅시다.

 

1
2
3
4
5
6
7
8
9
10
11
<script>
  document.addEventListener("DOMContentLoaded", () => {
    alert("DOM이 준비되었습니다!");
  });
</script>
 
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.3.0/lodash.js"></script>
 
<script>
  alert("라이브러리 로딩이 끝나고 인라인 스크립트가 실행되었습니다.");
</script>
cs

 

예시를 실행하면 "라이브러리 로딩이 끝나고…"가 먼저 보인 후 "DOM이 준비되었습니다!"가 출력되는 것을 확인할 수 있습니다. 스크립트가 모두 실행되고 난 후에야 DOMContentLoaded 이벤트가 발생한 것입니다.

※ DOMContentLoaded를 막지 않는 스크립트

위와 같은 규칙엔 두 가지 예외사항이 있습니다.

  1. async 속성이 있는 스크립트는 DOMContentLoaded를 막지 않습니다. 
  2. document.createElement('script')로 동적으로 생성되고 웹페이지에 추가된 스크립트는 DOMContentLoaded를 막지 않습니다.

[ DOMContentLoaded와 styles ] 

외부 스타일시트는 DOM에 영향을 주지 않기 때문에 DOMContentLoaded는 외부 스타일시트가 로드되기를 기다리지 않습니다.

그런데 가지 예외가 있습니다. 스타일시트를 불러오는 태그 바로 다음에 스크립트가 위치하면 스크립트는 스타일시트가 로드되기 전까지 실행되지 않습니다.

1
2
3
4
5
<link type="text/css" rel="stylesheet" href="style.css">
<script>
  // 이 스크립트는 위 스타일시트가 로드될 때까지 실행되지 않습니다.
  alert(getComputedStyle(document.body).marginTop);
</script>
cs

 

이런 예외는 스크립트에서 스타일에 영향을 받는 요소의 프로퍼티를 사용할 가능성이 있기 때문에 만들어졌습니다. 위 예시에선 스크립트에서 요소의 좌표 정보를 사용하고 있네요. 스타일이 로드되고, 적용되고 난 다음에야 좌표 정보가 확정되기 때문에 자연스레 이런 제약이 생겼습니다.

DOMContentLoaded는 스크립트가 로드되길 기다립니다. 위의 경우라면 당연히 스타일시트 역시 기다리게 됩니다.

※ 브라우저 내장 자동완성

Firefox와 Chrome, Opera의 폼 자동완성(form autofill)은 DOMContentLoaded에서 일어납니다.

페이지에 아이디와 비밀번호를 적는 폼이 있고, 브라우저에 아이디, 비밀번호 정보가 저장되어 있다면 DOMContentLoaded 이벤트가 발생할 때 인증 정보가 자동으로 채워집니다. 물론 사용자가 자동 완성을 허용했을 때의 이야기입니다.

 

따라서 실행해야 할 스크립트가 길어서 DOMContentLoaded 이벤트가 지연된다면 자동완성 역시 뒤늦게 처리됩니다. 브라우저 자동 완성 기능을 켜 놓은 사용자라면 특정 사이트에서 자동 완성이 늦게 처리되는 걸 경험해 보셨을 겁니다. 이런 사이트에선 페이지 로딩이 다 끝난 후에야 아이디나 패스워드 같은 브라우저에 저장한 정보가 폼에 뜹니다. 이런 지연이 발생하는 이유는 DOMContentLoaded 이벤트가 실행되는 시점 때문입니다.

 

[ window.onload ]

window 객체의 load 이벤트는 스타일, 이미지 등의 리소스들이 모두 로드되었을 때 실행됩니다. load 이벤트는 onload 프로퍼티를 통해서도 사용할 수 있습니다.

아래 예시에서 window.onload는 이미지가 모두 로드되고 난 후 실행되기 때문에 이미지 사이즈가 제대로 출력되는 것을 확인할 수 있습니다.

 

 

1
2
3
4
5
6
7
8
9
10
<script>
  window.addEventListener('load', (event=>
    alert('페이지 전체가 로드되었습니다.');
 
    // 이번엔 이미지가 제대로 불러와 진 후에 얼럿창이 실행됩니다.
    alert(`이미지 사이즈: ${img.offsetWidth}x${img.offsetHeight}`);
  };
</script>
 
<img id="img" src="https://en.js.cx/clipart/train.gif?speed=1&cache=0">
cs

[ window.onunload ] 

window 객체의 unload 이벤트는 사용자가 페이지를 떠날 때, 즉 문서를 완전히 닫을 때 실행됩니다. unload 이벤트에선 팝업창을 닫는 것과 같은 딜레이가 없는 작업을 수행할 수 있습니다.

 

그런데 분석 정보를 보내는 것은 예외사항에 속합니다.

사용자가 웹사이트에서 어떤 행동을 하는지에 대한 분석 정보를 모으고 있다고 가정해 보면, unload 이벤트는 사용자가 페이지를 떠날 때 발생하므로 자연스럽게 unload 이벤트에서 분석 정보를 서버로 보내는 게 어떨까 하는 생각이 듭니다.

 

메서드 navigator.sendBeacon(url, data)은 바로 이런 용도를 위해 만들어졌습니다.  

메서드에 대한 자세한 설명은 https://w3c.github.io/beacon/에서 찾아볼 수 있습니다.

 

sendBeacon는 데이터를 백그라운드에서 전송합니다. 다른 페이지로 전환 시 분석 정보는 제대로 서버에 전송되지만, 딜레이가 없는 것은 바로 이 때문입니다.

sendBeacon은 다음과 같이 사용할 수 있습니다.

 

  • 요청은 POST 메서드로 전송됩니다.
  • 요청 시 문자열뿐만 아니라 폼이나 fetch에서 설명하는 기타 포맷들도 보낼 수 있습니다. 대개는 문자열 형태의 객체가 전송됩니다.
  • 전송 데이터는 64kb를 넘을 수 없습니다.

sendBeacon 요청이 종료된 시점엔 브라우저가 다른 페이지로 전환을 마친 상태일 확률이 높습니다. 따라서 서버 응답을 받을 수 있는 방법이 없습니다. 사용자 분석 정보에 관한 응답은 대개 빈 상태입니다.

fetch 메서드는 '페이지를 떠난 후’에도 요청이 가능하도록 해주는 플래그 keepalive를 지원합니다. 

한편, 다른 페이지로 전환 중에 이를 취소하고 싶은 경우가 생기곤 합니다. unload에선 페이지 전환을 취소할 수 없고 onbeforeunload를 사용하면 가능합니다.

 

[ window.onbeforeunload ] 

사용자가 현재 페이지를 떠나 다른 페이지로 이동하려 할 때나 창을 닫으려고 할 때 beforeunload 핸들러에서 추가 확인을 요청할 수 있습니다.

beforeunload 이벤트를 취소하려 하면 브라우저는 사용자에게 확인을 요청합니다.

아래 예시를 실행하고, 브라우저에서 새로 고침을 해 직접 확인해 보시길 바랍니다. 

 

1
2
3
window.onbeforeunload = function() {
  return false;
};
cs

 

false 말고도 비어있지 않은 문자열을 반환하면 이벤트를 취소한 것과 같은 효과를 볼 수 있는데, 이는 특정한 이유 때문에 남아있는 기능입니다. 과거엔 문자열을 반환하면 브라우저에서 이 문자열을 보여줬었는데, 요즘은 이를 권장하지 않습니다.

 

예시를 살펴봅시다.

 

1
2
3
window.onbeforeunload = function() {
  return "저장되지 않은 변경사항이 있습니다. 정말 페이지를 떠나실 건 가요?";
};
cs

이렇게 문자열을 반환하도록 해도 얼럿창에 문자열이 보이지 않게 이유는 몇몇 사이트 관리자들이 오해가 생길 법하거나 성가신 메시지를 띄우면서 beforeunload 남용했기 때문입니다. 오래된 브라우저에서 예시를 실행하고 새로 고침을 누르면저장되지 않은…” 메시지가 뜨긴 합니다. 하지만 모던 브라우저에선 beforeunload 이벤트를 취소할 보이는 메시지를 커스터마이징 없습니다.

Comments