<script> 태그의 async와 defer
1. 위치에 따른 렌더링 시간
웹 브라우저의 역할은 웹 페이지를 이루고 있는 여러 파일을 다운로드 받은 후 HTML 문서를 위에서부터 한 줄씩 읽으며(parsing) 화면에 보여주는 것이라고 할 수 있습니다. HTML 파일에는 CSS, Javascript와 같은 여러 파일이 링크된 상태로 포함되어 있는데, 이러한 파일들은 해당 파일을 링크하는 코드가 읽힌 후 다운로드 됩니다. 먼저, <head>
태그에서 Javascript 파일이 링크된 경우를 살펴보겠습니다.
<head>
<title>Document</title>
<script src="js/app.js"></script>
</head>
<body>
<div></div>
</body>
웹 브라우저는 <head>
태그부터 차례대로 파싱하다가 <script>
태그를 만나면 해당 파일을 다운로드 하기 위해 잠시 HTML 파싱 과정을 멈춥니다. 다운로드가 완료되면 해당 Javascript 파일을 실행한 후 이어서 나머지 HTML 파일을 파싱합니다. 만약, 해당 Javascript 파일의 용량이 매우 큰 상황인 경우 웹 페이지를 화면에 나타내기까지 비교적 오랜 시간이 소요될 수 있습니다.
→ Parsing HTML
→ Stop Parsing HTML
→ fetching JS
→ executing JS
→ Continue Parsing HTML
→ End Parsing
→ Render Page
그렇다면, <body>
태그의 맨 끝 부분에서 Javascript를 불러오는 경우를 생각해봅시다.
<head>
<title>Document</title>
</head>
<body>
<div><div>
<script src="js/app.js"></script>
</body>
이 경우에는 웹 브라우저가 HTML 파싱을 모두 마친 후 Javascript 파일을 다운로드하기 때문에 앞의 상황에 비해 훨씬 빠르게 웹 페이지를 화면에 나타낼 수 있습니다. 그러나, 해당 웹 사이트의 주된 컨텐츠가 Javascript를 통해 생성되는 등 Javascript에 의존적인 경우 정상적인 웹 페이지를 화면에 나타내기까지 어느정도 시간이 소요된다는 단점이 있습니다.
→ Parsing HTML
→ End Parsing
→ fetching JS
→ executing JS
→ Render Page
2. async와 defer 속성에 따른 렌더링 시간
이번에는 ascync
로 Javascript 파일을 불러오는 경우를 살펴보겠습니다.
<head>
<title>Document</title>
<script async src="js/app.js"></script>
</head>
<body>
<div></div>
</body>
이 경우에는 웹 브라우저가 HTML 파싱 도중 <script>
태그를 만나면, HTML 파싱과 해당 Javascript 파일 다운로드를 병렬적으로 수행하고, 다운로드가 완료된 이후 파싱을 멈추고 해당 Javascript 실행합니다. 이어서, Javascript 실행이 종료된 후 파싱을 재개합니다. 이는 앞의 두 상황과는 다르게 Javascript 파일 다운로드를 병렬적으로 수행함으로써 파일 다운로드 시간을 절약할 수 있다는 장점이 있는 반면, 해당 Javascript 파일은 파싱이 완료되기 전에 실행되므로 아직 파싱되지 않는 DOM에 접근하는 경우 오류를 발생시킬 수 있습니다. 또한, 여전히 Javascript 실행은 독립적으로 수행되므로 웹 페이지를 화면에 나타내기까지 어느정도 시간이 소요된다는 단점도 있습니다.
→ Parsing HTML
→ fetching JS
→ Stop Parsing HTML
→ executing JS
→ Continue Parsing HTML
→ End Parsing
→ Render Page
그렇다면, defer
속성으로 Javascript 파일을 불러오는 경우를 살펴보겠습니다.
<head>
<title>Document</title>
<script defer src="js/app.js"></script>
</head>
<body>
<div></div>
</body>
이 경우에는 async
와 마찬가지로 HTML 파싱과 Javascript 파일 다운로드를 병렬적으로 수행하되, 다운로드가 완료된 이후 Javascript 파일을 실행하지 않고 HTML 파싱을 진행합니다. 그리고, 웹 페이지 렌더링 준비가 완료된 이후 다운로드 받은 Javascript를 실행합니다. 이 과정을 보면 알 수 있듯이 먼저 웹 페이지를 화면에 보여주고, 곧바로 Javascript를 실행시키므로 위의 다양한 방법들 중에서 가장 효율적인 방법이라고 할 수 있습니다.
→ Parsing HTML
→ fetching JS
→ End Parsing
→ Render Page
→ executing JS
3. async와 defer의 차이점
위에서는 async와 defer 속성을 통해 Javascript가 포함된 웹 페이지를 렌더링하는 부분에 대해서 살펴보았습니다. 이번에는 async과 defer가 각각 어떠한 방식으로 동작하는지 간단하게 정리해 보았습니다. 먼저, async를 통해 여러 개의 Javascript 파일을 실행시키는 순서를 살펴봅시다. 위에서 언급한 – HTML 문서를 위에서부터 한 줄씩 읽으며… – 내용을 토대로 아래 코드를 보면 a.js → b.js → c.js
순으로 실행될 것이라고 생각할 수 있습니다.
<head>
<title>Document</title>
<script async src="js/a.js"></script>
<script async src="js/b.js"></script>
<script async src="js/c.js"></script>
</head>
<body>
<div></div>
</body>
그러나, async
는 위의 경우에서 살펴보았듯이 다운로드는 병렬적으로 수행하고, 다운로드가 완료된 파일을 바로 실행시키므로 먼저 다운로드가 완료된 순으로 Javascript 파일을 실행시킵니다. 가령, 위의 Javascript 파일이 c.js → a.js → b.js
순으로 다운로드 된 경우 그 과정을 나타내면 다음과 같습니다.
→ Parsing HTML
→ fetching a.js
→ fetching b.js
→ fetching c.js
→ Stop Parsing HTML
→ executing c.js
→ Continue Parsing HTML
→ Stop Parsing HTML
→ executing a.js
→ Continue Parsing HTML
→ Stop Parsing HTML
→ executing b.js
→ Continue Parsing HTML
→ End Parsing
→ Render Page
이 경우, 먼저 실행된 c.js
가 a.js
에 의존적인 경우 아직 a.js
를 불러오지 못했으므로 오류를 발생시킬 수 있습니다. 반면, defer
속성을 적용시킨 경우에는 HTML 파싱과 Javascript 다운로드를 병렬적으로 수행하고, HTML 파싱을 완료한 이후에 Javascript를 실행시키므로 HTML에 명시한 순서대로 코드가 실행됩니다.
→ Parsing HTML
→ fetching a.js
→ fetching b.js
→ fetching c.js
→ End Parsing
→ Render Page
→ executing a.js다
→ executing b.js
→ executing c.js
따라서, HTML에서 Javascript를 불러올 때 <head>
태그 안에서 defer
속성을 적용시킨 방법이 가장 안정적이며, 효율적인 방법이라고 할 수 있습니다.