[Javascript] script async 와 defer의 차이점

devHagaa·2022년 4월 20일
0

Javascript

목록 보기
1/8

브라우저의 동작 방식

모던 웹브라우저에서 돌아가는 스크립트들은 대부분 HTML보다 ‘무겁다’. 용량이 커서 다운로드받는 데 오랜 시간이 걸리고 처리하는 것 역시 마찬가지이다.

사용자가 HTML파일을 다운로드 받았을 때 브라우저는 HTML을 한줄씩 읽으며 CSS와 병합하여 DOM 요소로 변환한다. HTML을 파싱하다가 <script>...</script> 태그를 만나면 HTML 파싱을 잠시 멈추고 스크립트를 먼저 실행해야 하므로 DOM 생성을 멈춘다. <script src="..."></script>를 만났을 때 외부에서 스크립트를 다운받고 실행한 후에 남은 페이지를 처리한다.

이런 브라우저의 동작 방식은 두 가지 중요한 이슈를 만든다.

  1. 스크립트에서는 스크립트 아래에 있는 DOM 요소에 접근할 수 없어 DOM 요소에 핸들러를 추가하는 등의 행위는 불가하다.

  2. 페이지 위쪽에 용량이 큰 스크립트가 있는 경우 스크립트가 페이지를 '막아버린다'. 페이지에 접속하는 사용자들은 스크립트를 다운받고 실행할 때까지 스크립트 아래쪽 페이지를 볼 수 없다.

<p>...스크립트 앞 콘텐츠...</p>

<script src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script>

<!-- 스크립트 다운로드 및 실행이 끝나기 전까지 아래 내용이 보이지 않습니다. -->
<p>...스크립트 뒤 콘텐츠...</p>




일반적으로 HTML에서 <script>태그의 위치를 <head>태그의 내부에 위치하게 한다.

<!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" />
    <title>Document</title>
    <script src="main.js"></script>
  </head>
  <body>
  	<div>내용</div>
  </body>
</html>

위의 2번 특징으로 인해 <head>태그 아래쪽에 위치한 <body>태그 내부의 요소들은 스크립트를 다운받는 동안 DOM 생성이 중단되기 때문에 페이지 로딩속도가 느리다는 단점이 있다.

그래서 그 다음으로 많이 하는 것이<body>태그의 내부 최하단에 <script>를 넣는 것이다.

<!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" />
    <title>Document</title>
  </head>
  <body>
  	<div>내용</div>
    <script src="main.js"></script>
  </body>
</html>

이 경우는 js를 받기 전에 이미 HTML파싱이 완료되었기 때문에 사용자들에게 페이지 컨텐츠를 보여줄 수 있다는 장점이 있지만, 만약 이 웹사이트가 js에 의존적이라면 사용자가 정상적인 페이지를 보기까지의 서버에서 js를 다운받고 실행하는 시간이 소요된다.

이러한 현상을 해결하기 위한 방법으로 제시되는 <script>태그의 옵션들이 있다.

async

<head>태그 내에 <script>를 작성하되 async옵션을 주는 것이다.
async옵션은 boolean타입의 속성값이기 때문에 선언하는 것만으로도 true로 설정된다.

<!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" />
    <title>Document</title>
    <script async src="main.js"></script>
  </head>
  <body>
  	<div>내용</div>
  </body>
</html>

브라우저가 HTML을 다운로드받아서 파싱하다가 <script>태그의 async를 만나면,

  1. main.js파일을 병렬로 다운로드 하자고 명령만 해두고 다시 HTML을 파싱하다가
  2. main.js의 다운로드가 완료되면
  3. 그 때 파싱하는 것을 멈추고
  4. main.js를 실행하고
  5. 실행이 완료되면 나머지 HTML을 다시 파싱한다.

이 방법을 사용했을 때 <body>끝에 선언하는 것 보다 HTML을 파싱하는동안 JS를 다운로드 받을 수 있어 다운로드 받는 시간을 절약할 수 있다는 장점이 있다. 하지만 HTML전체가 파싱되기 전에 JS가 실행되기 때문에 JS에서 DOM 요소를 조작하려 했을 때 해당 요소가 아직 정의되지 않은 상태일 수 있다. 또, JS를 실행하는 동안엔 파싱이 중단되어 페이지가 멈출 수 있기 때문에 사용자가 페이지를 보는 데 시간이 걸릴 수 있다.

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" />
    <title>Document</title>
    <script defer src="main.js"></script>
  </head>
  <body>
  	<div>내용</div>
  </body>
</html>

브라우저가 HTML을 다운로드받아서 파싱하다가 <script>태그의 defer를 만나면,

  1. main.js파일을 병렬로 다운로드 하자고 명령만 해두고 다시 HTML을 끝까지 파싱하다가
  2. 파싱이 끝난 후 사용자에게 페이지를 먼저 보여준 다음에
  3. 다운로드 되어진 main.js를 실행한다.

defer 옵션의 장점은 여러가지의 js파일을 다운로드 받아도 파싱 이후에 순서대로 js파일을 실행한다는 것이다.

이러한 옵션의 특징들을 살펴봤을 때 defer를 사용하는 것이 안정적이고 효율적으로 JS파일을 보여줄 수 있다는 것을 알 수 있다.

profile
디자이너인가 퍼블리셔인가 프론트엔드개발자인가 정체성의 혼란을 겪는 개린이

0개의 댓글