HTML에서 JavaScript 코드 로딩 시점
순차적으로 로딩되는 HTML
HTML에서 JavaScript 코드의 로딩은 HTML 코드가 작성된 순서대로 이루어진다. 순서대로 이루어진다는 것은 위에서 부터 아래로 내려가면서 구성요소를 인식한다는 뜻이다.
예를 들어 아래와 같은 HTML 파일이 있다고 해보자. 이 HTML에서는 head부터에서 script가 추가되는데 이 스크립트는 numPlayers라는 변수를 가지고 이 변수는 document에서 "num-players"라는 아이디를 가진 구성요소를 가져온다.
<!DOCTYPE html>
<html lang="en">
<head>
<script>
const numPlayers = document.getElementById("num-players")
</script>
</head>
<body>
<label>참가자 수: <input type="number" id="num-players"></label>
</body>
</html>
하지만 이 코드에서 numPlayers는 유효하지 않다. 이유는 num-players라는 아이디를 가진 구성요소가 body부에 존재하기 때문이다. 이 때문에 numPlayers는 아무런 값도 가져오지 못한다.
이 때문에 해당 값을 로깅 해보면 null 출력된다.
<!DOCTYPE html>
<html lang="en">
<head>
<script>
const numPlayers = document.getElementById("num-players")
console.log(numPlayers) // null 출력
</script>
</head>
<body>
<label>참가자 수: <input type="number" id="num-players"></label>
</body>
</html>
간단하게 해결하는 방법
HTML은 순차적으로 로딩되므로, 가장 쉬운 방법은 num-players라는 이름을 가진 구성요소 아래 부분에 script를 정의하는 것이다. 그러면 해당 구성요소를 인식해서 가져올 수 있다.
<body>
<label>참가자 수: <input type="number" id="num-players"></label>
<script>
const numPlayers = document.getElementById("num-players")
console.log(numPlayers) // <input type="number" id="num-players"> 출력
</script>
</body>
하지만 이 방법은 몇가지 문제가 있다. 코드를 매번 body에 추가하면 어디서 사용되었는지 확인이 어려워지고 매번 순차적으로만 수행할 수 없다.
이를 해결하기 위해 HTML에서는 JavaScript를 로딩하는 시점을 제어하는 몇가지 옵션을 제공한다. 이제 그 옵션들을 알아보도록 하자.
defer과 async 사용한 HTML에서 JavaScript 코드 로딩 시점과 방식 변경
JavaScript에서는 <script></script> 태그 안에 들어간 JavaScript 코드의 로딩 시점을 변경하는 두가지 옵션 defer, async를 제공한다.
이 두 옵션을 사용하기 위해서는 javascript source를 만들어야 한다. 다음과 같은 script.js 파일을 만들도록 하자.
const numPlayers = document.getElementById("num-players")
console.log(numPlayers)
준비가 완료됐으면 defer을 사용하는 방법부터 보도록 하자.
defer
defer을 속성을 사용하면 HTML Parser가 문서를 파싱하는 동안 JavaScript 코드가 백그라운드에서 로드된다. 이렇게 하면 문서의 로딩 시간이 더 빨라진다. 또한 이 속성을 사용하면 문서가 파싱(로딩)되는 동안은 JavaScript 코드가 실행되지 않고 파싱이 완료된 후 실행된다.
defer은 script 뒤에 넣는 것과 파일명 뒤에 넣는 방법 두가지로 사용할 수 있다.
- script 뒤에 넣기
<script defer src="script.js"></script>
- 파일명 뒤에 넣기
<script src="script.js" defer></script>
아래와 같이 코드를 만들어 실행해보자. 그러면 script가 head부에 있음에도 body부가 모두 로딩된 후 실행되어 <input type="number" id="num-players"> 가 출력되는 것을 볼 수 있다.
<!DOCTYPE html>
<html lang="en">
<head>
<script defer src="script.js"></script> <!-- <input type="number" id="num-players"> 출력 -->
</head>
<body>
<input type="number" id="num-players">
</body>
</html>
async
async는 defer과 다르게 js script 다운로드가 완료되면 즉시 실행된다. async를 사용하면 스크립트가 실행되는 순서가 보장되지 않으므로 DOM 부분을 조작하는 스크립트에서는 사용을 지양해야 한다.
async도 script 뒤에 넣는 것과 파일명 뒤에 넣는 방법 두가지로 사용할 수 있다.
- script 뒤에 넣기
<script async src="script.js"></script>
- 파일명 뒤에 넣기
<script src="script.js" async></script>
이 코드를 아래와 같이 실행해보면 null이 출력될 수도 있고 <input type="number" id="num-players"> 이 출력될 수도 있다.
<!DOCTYPE html>
<html lang="en">
<head>
<script async src="script.js"></script>
</head>
<body>
<input type="number" id="num-players">
</body>
</html>
따라서 주의가 필요하며 JavaScript 코드만 조작하는 경우에만 사용해야 한다.
defer과 async의 의의
HTML파서가 순차적으로 파싱하게 되면 이는 스크립트가 로딩될 때까지 기다린다는 뜻이다. 하지만, defer과 async를 사용하면 백그라운드에서 스크립트 다운로드 작업이 일어나며, HTML 파서는 그대로 파싱 작업을 진행해 스크립트 로딩을 더 빠르게 최적화 시킬 수 있다.
요즘은 워낙 컴퓨터 성능이 좋아서 그런 일이 웬만해서는 일어나지 않지만 script 양이 너무 많아지면 defer과 async를 사용하지 않으면 브라우저의 로딩 속도가 매우 느려질 수 있다.