

브라우저가 HTML 파일을 만났을 때, 위에서부터 아래로 순차적으로 코드를 파싱한다. 이때, script 태그를 만나면 HTML 파싱을 잠시 멈추고 스크립트 파일을 가져온다. 그 후에, 스크립트 코드를 실행한다. 스크립트 코드 실행이 끝난 후에, 다시 HTML 파싱을 재개한다.

HTML 파일에서 button 태그를 파싱하여 DOM 요소를 생성하기도 전에, 스크립트 파일에서 버튼 요소를 가져올 수 없다. 따라서 에러가 발생한다.
let btn = document.querySelector('#btn');
console.log('btn: ', btn); // btn: null
실제로 btn은 버튼 요소를 가져오지 못하고 null을 출력한다.
따라서 스크립트 로드를 해결하기 위해서 보통 아래와 같은 방법을 사용한다.
브라우저의 렌더링 엔진이 HTML 코드를 해석하고 객체화하는 과정을 말한다.
스크립트를 로드한다는 말은 JavaScript 파일을 가져오고, 브라우저가 해당 파일을 실행한다는 뜻이다.
🔽 practice.html
<!DOCTYPE html>
<html lang="en">
<head>
</head>
<body>
<button id="btn">버튼</button>
<script>
let btn = document.querySelector('#btn');
btn.addEventListener('click', function () {
alert('Hello World!');
});
</script>
</body>
</html>
HTML 파싱을 한 후에 script 태그를 로드할 수 있도록 body 태그 최 하단으로 script 태그 위치를 이동시키는 방법이다.

에러 발생 없이 정상적으로 작동한다.
window.onload: HTML 파싱, DOM 생성 그리고 외부 콘텐츠(images, script, css, etc)가 로드된 후 실행되는 이벤트를 정의할 수 있게 해주는 이벤트 리스너이다.
🔽 practice.html
<!DOCTYPE html>
<html lang="en">
<head>
<script src="practice.js"></script>
</head>
<body>
<button id="btn">버튼</button>
</body>
</html>
🔽 practice.js
window.onload = function() {
let btn = document.querySelector('#btn');
btn.addEventListener('click', function () {
alert('Hello World!');
});
};
onload 이벤트 리스너의 경우 비효율적일 수 있다. 예를 들어, 외부 콘텐츠로 이미지를 100개 가져온다고 생각해보자. 스크립트가 실행되기 위해서는 이미지 100개를 가져올 때까지 기다려야 하기에 비효율적이다.
DOMContentLoaded: HTML 파싱, DOM 생성 후 발생하는 이벤트이다. 외부 컨텐츠가 로드되는 것은 기다리지 않는다.
🔽 practice.html
<!DOCTYPE html>
<html lang="en">
<head>
<script src="practice.js"></script>
</head>
<body>
<button id="btn">버튼</button>
</body>
</html>
🔽 practice.js
document.addEventListener('DOMContentLoaded', function() {
let btn = document.querySelector('#btn');
btn.addEventListener('click', function () {
alert('Hello World!');
});
});
🔽 practice.html
<!DOCTYPE html>
<html lang="en">
<head>
<script src="practice.js"></script>
</head>
<body>
<button id="btn">버튼</button>
</body>
</html>
🔽 practice.js
window.onload = function() {
alert('onload');
};
document.addEventListener('DOMContentLoaded', function() {
alert('DOMContentLoaded');
});


DOMContentLoaded가 먼저 실행되고, 외부 콘텐츠가 로드된 후에 스크립트를 실행하는 onload 이벤트가 실행된다.

앞선 script로드 해결 방법들은 HTML 파싱을 완료하고 DOM 요소가 생성된 후에 스크립트 코드를 가져오고, 가져온 스크립트를 실행한다. (onload 이벤트 리스너의 경우 외부 콘텐츠도 다운 받은 후에 스크립트를 가져와 실행한다.) 따라서 스크립트 파일에서 DOM 요소에 원활하게 접근할 수 있다.
하지만, HTML을 파싱할 때 스크립트를 실행하진 못하더라도 스크립트를 다운 받을 수 있다면 효율적이지 않을까? 따라서 HTML5에서 등장한 defer와 async 속성이 있다. 이 속성을 사용하면 자바스크립트 파일을 효과적으로 가져올 수 있다.
HTML 파싱과 함께, 비동기로 JavaScript 파일을 불러온다.
🔽 practice.html
<!DOCTYPE html>
<html lang="en">
<head>
<script src="practice.js" defer></script>
</head>
<body>
<button id="btn">버튼</button>
</body>
</html>

HTML 파싱이 완료된 후, JavaScript 코드를 실행한다. HTML 파싱이 완료된 후에, JavaScript 코드에서 DOM 요소를 가져오기 때문에 에러가 발생하지 않는다.
HTML 파싱과 함께 비동기로 JavaScript 파일을 불러온다. HTML 파싱이 완료되지 않았더라도, 먼저 다운로드가 완료된 JavaScript 파일부터 실행이 시작된다. JavaScript 파일을 실행할 때는 HTML 파싱이 중단된다.
🔽 practice.html
<!DOCTYPE html>
<html lang="en">
<head>
<script src="practice.js" async></script>
</head>
<body>
<button id="btn">버튼</button>
</body>
</html>

HTML 파싱을 하다가, script 태그를 만나면 HTML 파싱 작업을 멈추지 않고 비동기로 스크립트를 가져온다. 그 후, 스크립트를 실행할 때는 HTML 파싱 작업이 중지된다. 따라서, 스크립트에서 DOM 요소를 가져오고자 할때 HTML 태그의 순서에 따라서 원하는 요소를 가져오지 못할 수도 있다. 왜냐하면 가져오고자 하는 요소가 미처 파싱되지 않았을 수도 있기 때문이다.
🚀따라서, async는 꼭 필요한 순간에 사용한다. 일반적으로는 스크립트 파일을 효과적으로 불러오기 위해서 defer 속성을 사용한다.