defer와 async script

Plato·2022년 7월 17일
0


TL;DR: defer와 async 속성을 사용하여, 스크립트가 html 파싱을 멈추지 않도록 막을 수 있다. 지연된 스크립트는 dom 생성과 DOMContentLoaded 이벤트 발생 사이에 스크립트를 실행한다. 비동기 스크립트의 경우, 스크립트가 다운로드되는 동안에, 브라우저가 html을 파싱하고, 스크립트의 다운로드가 완료되면 스크립트를 실행한다.

html 명세에 의하면, 스크립트 태그는 html 파싱을 중지시킨다. 물론 현대 브라우저들은 pre-loading 등을 통해, 파싱이 중지된 상태에서도, 추가적으로 http 요청을 통해 리소스를 다운로드하는 등의 일을 하지만, 파싱을 중지시키지 않는 것이 좋다. 이번에는, 논-블로킹 스크립트 태그를 만드는 방법을 살펴보자.

deferred script

스크립트의 defer 속성을 명시하면, 해당 스크립트는 dom 생성과 DOMContentLoaded 이벤트 발생 사이에 스크립트를 실행한다. 이때, CSSOM의 생성을 먼저 진행하는지, deferred script를 먼저 실행하는지, 아직 알아내지 못했다. 알아내는 대로, 해당 내용을 추가하도록 하겠다.

<html>
  <head>
    <script src="./테스트.js"></script>
    <link rel="stylesheet" href="./짧은.css" >
    <body>
      defer를 쓰지 않은 경우
    </body>
  </head>
</html>
(function(){
const $body = document.querySelector('body');
console.log($body); // null
})();

위의 경우, 외부 스크립트를 연결하는 <script> 태그가, <body> 태그보다 위에 있다. 여기에서, console.log($body) 라인이 null을 프린트함에 주목하자. 스크립트 태그는 html의 파싱을 중지시키기 때문에, 스크립트 태그 아래에 있는 태그를 아직 파싱하지 못해서 생긴 일이다. 이때, 아래처럼 defer를 사용하여 이 버그를 수정할 수 있다.

<html>
  <head>
    <script src="./테스트.js" defer></script>
    <link rel="stylesheet" href="./짧은.css" >
    <body>
      defer를 쓴 경우
    </body>
  </head>
</html>

defer를 썼기 때문에, 외부 스크립트가 실행되는 시점에는, 이미 DOM의 생성이 완료된 상태이다. 그래서, console.log($body)라인이, body 엘리먼트를 잘 프린트한다.

async script

스크립트 태그의 async 속성을 명시하면, 외부 스크립트의 다운로드가 완료되기 전까지, html 파싱을 막지 않는 것이 특징이다. 이때, 다운로드가 완료되면, 실행이 완료될 때까지, html 파싱이 중지되는 점은, defer 속성을 사용하지 않았을 때와 동일하다.

  <html>
  <head>
    <script src="./테스트.js" async></script>
    <link rel="stylesheet" href="./짧은.css" >
    <body>
      <script>
        for(let i = 0; i < 10000; i++){
          1+1;
        }
      </script>
      <div class="last-div"></div> <!-- 외부 스크립트에서, 이 요소를 프린트하려 하지만, null을 프린트한다. -->
    </body>
  </head>
</html>
(function(){
const $body = document.querySelector('body');
const $last = document.querySelector('.last-div');
console.log($body); // body를 잘 프린트한다.
console.log($last); // null이 나온다.
})();

위의 스크립트에서, body는 잘 프린트함을 확인할 수 있다. 그 이유는, 해당 외부 스크립트를 다운로드 받는 중에, 이미 태그를 파싱했기 때문이다. 하지만, 'last-div'를 클래스로 갖는 div 태그가 등장하기 전에, 스크립트 태그에서 for loop을 돌리면서 시간을 낭비한다. 그래서 div 태그를 파싱하기 전에, 외부 스크립트의 다운로드가 완료되고, 파싱이 중지되면서, 외부 스크립트에서 div 요소를 프린트하지 못한다.

마무리

html 파싱이 완료돼서, DOM을 생성해야지, 나머지 크리티컬 렌더링 패스를 거쳐서, 렌더링을 할 수 있다. 만약, html 파싱을 블로킹하는 스크립트 태그를 잘못 사용하면, 스크립트가 다운로드되고 실행되는 시간만큼 렌더링이 늦어진다. defer와 async 속성을 적재적소에 명시함으로써, 성능을 개선할 수 있음을 기억하자.

0개의 댓글