Defer / Async 효율적으로 사용하기

AtoZ·2023년 1월 28일
0

프론트엔드

목록 보기
3/3
post-thumbnail

안녕하세요 😀

한동안 웹 최적화에 대해서 관심이 있었는데 그 분야와 연관성이 있는 defer와 async에 대해서 알아보겠습니다.

글은 크게 아래와 같은 소주제로 진행하겠습니다.

  1. JS는 언제 다운로드 받을까?
  2. defer / async 의 정의와 어떤 차이가 있을까?
  3. 각 속성을 적절하게 사용하는 방법은 뭘까?
  4. 마무리

JS는 언제 다운로드 받을까?

일전에 브라우저 렌더링 하는 과정에 대해서 공유해 드린 적이 있는데 그 부분에서 HTML이나 CSS에 대해서는 언급이 있었지만, JS에 대한 언급은 없었습니다. 그래서 JS는 언제 다운로드 받고 어떻게 적용되는지 궁금하실 수도 있을거 같아요.

보통 HTML을 다운로드 받고 HTML에서 import 한 JS를 다운로드 받습니다.

그런데 JS는 항상 같은 순서로 다운로드 받지 않습니다. 이게 무슨 말이냐면 HTML 내에서 import 한 위치에 따라 또는 import한 방식에 따라 다운로드 받는 순서가 다릅니다.

defer / async 의 정의와 어떤 차이가 있을까?

HTML 파일 parsing 하다가 <script>….<script> 태그를 만나면 스크립트를 먼저 실행해야 하므로 DOM 생성을 멈춥니다. 아래 그림을 보시면 index.html을 다운로드 받고 Main 탭 첫 번째 박스에서 parseHTML 작업을 시작하는데 중간에 script가 import 되어있어 해당 부분을 실행합니다. 그리고 실행 후에는 하지 못했던 parseHTML 작업을 마저 실행합니다.

이런 방식이기 때문에 HTML 내에서 <script>….<script> 태그를 body 태그의 맨 아래에 두거나 defer나 async 같은 키워드를 사용하여 이 문제점을 해결합니다. 각각의 방법에 대해서 장단점을 파악해보겠습니다.

1. <script>….<script> 태그를 body 태그의 맨 아래 두는 방법

이런 방법을 사용할 때 문제점은 HTML 파일의 용량이 대용량일 때 스크립트 처리가 늦어진다는 겁니다.

자세히 살펴보면 HTML 파일이 대용량이라면 HTML 파일을 다운로드 받는데 시간이 들고, HTML 다운로드가 완료되면 스크립트 파일을 다운로드 받아야 하는데 이렇게 하면 사용자 입장에서 시간이 오래 걸린다고 느낄 수 있습니다.

그러면 HTML parsing 하면서 스크립트도 다운받으면 되지 않나? 라고 생각하시는 분이 계실 텐데 맞습니다. 이 방법이 효과적이고 실행시키고자 나온 키워드가 defer 입니다.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="assets/styles/app.css" />
    <title>Document</title>
  </head>
  <body>
    테스트입니다.
    <script src="assets/scripts/vendor.js"></script>
    <script src="assets/scripts/app.js"></script>
  </body>
</html>

2. Defer

defer의 사전적 의미는 지연되다 라는 뜻인데 script 태그의 속성에서 defer는 스크립트 실행을 페이지 구성이 끝날 때까지 지연시키게 한다는 뜻입니다.

HTML 파일이 대용량인 경우 <script>….<script> 태그 실행 시점을 늦춘다는 문제점이 있었는데 근본적으로 body 태그 맨 하단에 <script>….<script> 태그를 둔 이유를 보면 HTML parsing의 우선순위가 문제였습니다.

HTML parsing 할 때 <script>….<script> 태그를 만나면 parsing을 멈추고 <script>….<script> 태그를 먼저 처리하는 로직이 문제입니다. 그래서 HTML parsing을 하고 있을때 <script>….<script> 태그를 만나더라도 실행시키지는 않고 백그라운드에서 미리 다운로드 받으면 이 문제를 해결할 수 있습니다.

단, defer는 외부 스크립트에만 유효한 속성이며 만약 src 속성이 없다면 defer는 자연스럽게 무시됩니다 ㅎㅎ

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<link rel="stylesheet" href="assets/styles/app.css" />
    <script defer src="assets/scripts/vendor.js"></script>
    <script defer src="assets/scripts/app.js"></script>
    <title>Document</title>
  </head>
  <body>
    테스트입니다.
  </body>
</html>

defer를 사용했을 때는 아래처럼 HTML parse 실행을 중단시키지 않고 백그라운드에서 JS를 다운로드하는것을 보실 수 있습니다. 다운로드 끝난 후에도 HTML parse 작업은 완료했기 때문에 추가로 진행되지 않는 것을 보실 수 있습니다.

3. async

async도 defer와 마찬가지로 백그라운드에서 다운로드 됩니다. 그러나 async 속성이 있는 스크립트가 있을 때에는 HTML 파싱이 멈춥니다. 이렇게 말씀드리면 async 속성 사용하는 이유가 없는 거 아닌가? 라고 생각하실 수도 있는데 async 만의 장점이 있습니다.

async는 이름에서 느껴지듯이 비동기 처리됩니다.

여기서 말하는 비동기는 aysnc 속성이 붙은 스크립트에 대해서입니다.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<script async src="https://javascript.info/article/script-async-defer/long.js"></script>
		<script async src="https://javascript.info/article/script-async-defer/small.js"></script>
    <title>Document</title>
  </head>
  <body>
    테스트입니다.
  </body>
</html>

만약 위와 같이 스크립트가 여러 개 있는 상황일 때 스크립트의 순서가 보장되지 않습니다. 스크립트가 모두 다운로드 된 순서에 따라 제각각 스크립트는 실행이 됩니다. 이렇게 먼저 로드가 된 스크립트가 먼저 실행되는 것을 'load-first order’라고 부릅니다. 이런 특성은 서드파티 스크립트를 참조하는 새로운 스크립트를 개발하는 단계에서 유용합니다. 추가로 async 속성이 기본이 되는 순간이 있습니다. 바로 동적 스크립트를 사용할 때 입니다.

function appendScript() {
		let script = document.createElement('script');
		script.src = "/article/script-async-defer/long.js";
		document.body.append(script); // 여기
}

appendScript();

동적 스크립트인 appendScript()가 호출되면 script는 async 속성이 자동으로 부여됩니다. 그래서 ‘load-first’ order 현상이 발생하게 되는데 이에 따라서 개발자가 원하지 않는 현상이 나타날 수 있습니다. 만약 async 속성을 제거하고 싶다면 script.async = false 를 추가해줘야 합니다.

각 속성을 적절하게 사용하는 방법은 뭘까?

실무에선 defer를 DOM 전체가 필요한 스크립트나 실행 순서가 중요한 경우에 적용합니다. async는 방문자 수 카운터나 광고 관련 스크립트같이 독립적인 스크립트에 혹은 실행 순서가 중요하지 않은 경우에 적용합니다.

해당 그림은 <script> 태그를 넣는 상황에 따른 순서입니다.

마무리

최적화와 관련되서 defer와 async를 알아봤는데 올바른 시점에 JS 파일을 다운로드 받는것이 얼마나 중요한지 알게됐습니다. 추가로 최근에 동적 스크립트를 사용한적이 있었는데 무심결에 사용했던 코드였는데 그때 async로 스크립트를 다운받았겠구나라는 생각을 하게 되는 시간이였습니다.

참조

https://ko.javascript.info/script-async-defer

profile
코딩으로 글쓰는 작가

0개의 댓글