

웹 브라우저는 HTML 을 해석(파싱)하는 과정 중 <script> 를 만나면 잠시 HTML 해석을 멈추고 <script> 요소를 먼저 실행한다. <script> 요소는 등장과 동시에 실행이 되기 때문에 HTML 내 위치에 따라 가끔 문제를 일으키기도 한다.
<script> 는 Body 태그가 끝나기 전에 넣자문제는 HTML 을 파싱하면서 DOM 트리를 생성해야만 javascript 로 DOM 을 오류없이 조작할 수 있는데 파싱이 트리가 생성되기 전에 <script> 가 실행이 되면 버그가 생길 수 있다. 따라서 스크립트의 위치는 Body 태그가 끝나기 전 위치에 넣는것이 옳다.
<script> 의 속성에는 async 와 defer 두 가지가 있다. <script> 를 Body 태그가 끝나기 전 위치에 넣는것이 옳지만 경우에 따라서는 스크립트를 먼저 실행해야 하는 경우도 생긴다. 해결책은 다음과 같다.
<script async src="script.js">

<script defer src="script.js">

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<head>
<meta charset="UTF-8" />
</head>
<body>
<script>
window.onload = function () {
console.log("after window load");
console.log(document.querySelector("#div"));
};
document.addEventListener("DOMContentLoaded", function () {
console.log("after dom load");
console.log(document.querySelector("#div"));
});
console.log("script 실행 시작");
console.log(document.querySelector("#div"));
</script>
<div id="div">div</div>
</body>
</body>
</html>
위 코드는 script태그가 div 태그 보다 위에 있다. 하지만 DOMContentLoaded & onload 를 이용하면 오류 없이 div 를 읽을 수 있다. 위 html 을 브라우저로 실행 시켰을 때 콘솔의 결과는 아래와 같다.

브라우저 동작 방식을 다시 설명하면 아래와 같다.
- HTML을 읽기 시작한다.
- HTML을 파싱한다.
- DOM 트리를 생성한다.
- Render 트리(DOM tree + CSS의 CSSOM 트리 결합)가 생성되고
- Display에 표시한다.
이 때 DOMContentLoaded 의 콜백은 DOM 트리가 생성이 끝난 후에 실행되고(3번 이후) (위 onload 의 콜백은 문서에 포함된 모든 콘텐츠(images, script, css, ...)가 전부 로드된 후 (4번 이후) 실행되는 차이가 있다.