본문 바로가기

Development/JavaScript

JavaScript 위치 및 async/defer 속성에 따른 HTML 렌더링 효율성

위치에 따른 렌더링 시간

웹 브라우저의 역할은 서버로부터 웹 페이지를 이루고 있는 여러 파일을 전달(다운로드) 받은 후 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

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 

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.jsa.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 속성을 적용시킨 방법이 가장 효율적인 방법이라고 할 수 있다.