DOMContentLoaded
이벤트는 브라우저가 HTML을 전부 읽고 DOM 트리를 완성하는 즉시 발생한다. 즉, DOMContentLoaded 핸들러는 문서가 로드되었을 때 실행된다. 다만, 이미지 파일이나 스타일시트 등의 로드는 기다리지 않는다는 특징이 있다.
DOMContentLoaded 이벤트는 document 객체에서 발생하기 때문에 addEventListener를 사용해야 한다.
document.addEventListener("DOMContentLoaded", event);
브라우저는 HTML 문서를 처리하는 도중에 <script> 태그를 만나면, DOM 트리 구성을 멈추고 <script>를 먼저 실행하고, 그 후에 나머지 HTML 문서를 처리한다. 따라서 DOMContentLoaded 이벤트는 <script> 안에 있는 스크립트가 처리되고 난 후에 발생한다.
<script> // 실행 순서 2
document.addEventListener("DOMContentLoaded", () => {
alert("DOM 트리 구성 완료. DOMContentLoaded 이벤트 발생.");
});
</script>
<script> // 실행 순서 1
alert("DOM 트리 구성 중. 스크립트 먼저 실행.");
</script>
DOMContentLoaded를 막지 않는 2가지 예외 사항
1.async
속성이 있는 스크립트는 DOMContentLoaded를 막지 않는다.
2.document.createElement('script')
로 동적으로 생성 되고 웹페이지에 추가된 스크립트는 DOMContentLoaded를 막지 않는다.
앞서 말했듯이 DOMContentLoaded는 스타일 시트의 로드는 기다리지 않는다. 이는 외부 스타일시트는 DOM에 영향을 주지 않기 때문이다.
아직 이미지가 로드되기 전이기 때문에 이미지 사이즈가 0으로 출력되는 것을 볼 수 있다.
하지만, 스타일시트를 불러오는 태그 바로 다음에 스크립트가 위치하면 이 스크립트는 스타일시트가 로드되기 전까지 실행되지 않는다. 이는 스크립트에서 스타일에 영향을 받는 요소의 프로퍼티를 사용할 가능성이 있기 때문이다.
<link type="text/css" rel="stylesheet" href="style.css">
<!-- 스타일 시트가 로드되고 나서 스크립트 실행 -->
<script>
alert(getComputedStyle(document.body).marginTop);
//요소의 좌표 정보 -> 스타일이 로드되고 난 후에야 확정됨
</script>
document.readyState
프로퍼티는 문서의 로딩 상태를 알려주며, 프로퍼티의 값은 3가지가 있다.
① loading
– 문서 로딩 중
② interactive
– 문서 로딩 완료
③ complete
– 문서를 비롯한 이미지 등의 리소스들까지 모두 로딩 완료
window 객체의 load
이벤트는 DOMContentLoaded와 달리 스타일, 이미지 등의 리소스들이 모두 로드되었을 때 실행된다.
window 객체의 unload
이벤트는 사용자가 페이지를 떠날 때, 즉 문서를 완전히 닫을 때 실행된다.
load, unload 이벤트는 onload
, onunload
프로퍼티를 통해서도 사용할 수 있다.
window.addEventListener("load", event);
window.addEventListener("unload", event);
이미지까지 모두 로드되길 기다렸다가 로드가 완료되고 난 후 이미지 사이즈를 정상적으로 출력하는 것을 볼 수 있다.
beforeunload
이벤트는 unload와 마찬가지로 사용자가 페이지를 떠날 때 발생한다. 하지만, unload에선 페이지 전환을 취소할 수 없고 onbeforeunload에선 취소가 가능하다는 차이점이 있다.
window.onbeforeunload = function() {
return "정말 페이지를 떠나실 건 가요?";
};
과거에는 위와 같이 사용자 지정 문구를 지원했었다. 그러나 현재 대부분의 브라우저에서는 사용자 지정 문구를 사용하지 않으며 이 동작을 지원하지 않는다.
위에서 언급했듯이 브라우저는 HTML을 읽다가 <script> 태그를 만나면 스크립트를 먼저 실행하므로 DOM 트리 구성을 멈춘다. 이는 src 속성이 있는 외부 스크립트 <script src=""></script>를 만났을 때도 마찬가지로 적용된다. 외부에서 스크립트를 다운받고 실행한 후에야 남은 페이지를 처리할 수 있다.
이런 브라우저의 동작 방식으로 인해 2가지 문제점이 발생한다.
① 스크립트에서는 스크립트 아래에 있는 DOM 요소에 접근할 수 없다. 따라서 DOM 요소에 핸들러를 추가하는 것과 같은 여러 행위가 불가능하다.
② 페이지 위쪽에 용량이 큰 스크립트가 있는 경우 스크립트를 다 다운받고 실행할 때까지 스크립트 아래쪽 페이지를 볼 수 없다.
단순하게 스크립트를 페이지 맨 아래 위치 시켜서 문제를 해결할 수도 있다. 하지만 이는 근본적인 해결방안은 아니다. HTML 문서가 매우 방대한 경우, 브라우저가 HTML 문서 전체를 다운로드 한 다음에 스크립트를 다운받게 되면 페이지가 엄청 느려질 것이기 때문이다.
위 문제를 해결하기 위해선 script 속성으로 defer
와 async
사용하면 된다.
defer
스크립트를 백그라운드에서 다운로드된다. 따라서 스크립트에 defer 속성을 넣어주게 되면 스크립트를 다운로드하는 와중에도 HTML 파싱을 멈추지 않는다. 그리고 defer 스크립트 실행은 페이지 구성이 끝날 때까지 지연 된다.
<script defer src="#"></script>
async
스크립트는 페이지와 완전히 독립적으로 동작한다. defer 스크립트와 마찬가지로 백그라운드에서 다운로드된다. 따라서 스크립트에 async 속성을 넣어주게 되면 스크립트를 다운로드하는 와중에도 HTML 파싱을 멈추지 않는다.
<script async src="#"></script>
defer와 async는 다운로드 시 페이지 렌더링을 막지 않는다는 공통점이 있지만, DOMContentLoaded 이벤트가 기다리냐 기다리지 않느냐의 차이가 있다.
- DOMContentLoaded 이벤트는 defer 스크립트의 실행을 기다린다.
- DOMContentLoaded 이벤트와 async 스크립트는 서로를 기다리지 않는다.
- 페이지 구성이 끝난 후에 async 스크립트 다운로딩이 끝난 경우, DOMContentLoaded는 async 스크립트 실행 전에 발생할 수 있다.
- async 스크립트가 짧아서 페이지 구성이 끝나기 전에 다운로드 되거나 스크립트가 캐싱처리 된 경우, DOMContentLoaded는 async 스크립트 실행 후에 발생할 수도 있다.
async 스크립트는 다른 스크립트들과도 마찬가지로 서로 기다리지 않고 독립적으로 동작한다. 때문에 페이지에 async 스크립트가 여러 개 있는 경우, 실행 순서가 제각각이 되어 다운로드가 끝난 스크립트 순으로 진행된다.
공통점
defer와 async 스크립트 속성을 사용하면 다운로드 시 페이지 렌더링을 막지 않는다.
실행 방식의 차이점
• defer 스크립트는 DOMContentLoaded 이벤트가 실행을 기다려주기 때문에 문서 다운로드와 파싱이 완료된 후에, DOMContentLoaded 이벤트 발생 전에 실행된다.
• async 스크립트는 페이지와 완전히 독립적으로 동작하므로 HTML 문서가 완전히 다운로드되지 않은 상태라도 로드 및 실행될 수 있다. 다른 스크립트들이나 DOMContentLoaded 이벤트와 서로 기다리지 않는다.
동작 순서의 차이점
• defer 스크립트는 문서에 추가된 순으로 동작한다.
• async 스크립트는 먼저 다운로드된 스크립트순으로 동작한다. (문서 내 순서와 상관X)
참고 자료
🔗 문서와 리소스 로딩