모던 웹브라우저에서 돌아가는 스크립트들은 대부분 HTML보다 ‘무겁다’. 용량이 커서 다운로드받는 데 오랜 시간이 걸리고 처리하는 것 역시 마찬가지이다.
사용자가 HTML파일을 다운로드 받았을 때 브라우저는 HTML을 한줄씩 읽으며 CSS와 병합하여 DOM 요소로 변환한다. HTML을 파싱하다가 <script>...</script> 태그를 만나면 HTML 파싱을 잠시 멈추고 스크립트를 먼저 실행해야 하므로 DOM 생성을 멈춘다. <script src="..."></script>를 만났을 때 외부에서 스크립트를 다운받고 실행한 후에 남은 페이지를 처리한다.
이런 브라우저의 동작 방식은 두 가지 중요한 이슈를 만든다.
스크립트에서는 스크립트 아래에 있는 DOM 요소에 접근할 수 없어 DOM 요소에 핸들러를 추가하는 등의 행위는 불가하다.
페이지 위쪽에 용량이 큰 스크립트가 있는 경우 스크립트가 페이지를 '막아버린다'. 페이지에 접속하는 사용자들은 스크립트를 다운받고 실행할 때까지 스크립트 아래쪽 페이지를 볼 수 없다.
<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>태그의 옵션들이 있다.
<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를 만나면,
main.js파일을 병렬로 다운로드 하자고 명령만 해두고 다시 HTML을 파싱하다가 main.js의 다운로드가 완료되면 main.js를 실행하고 
이 방법을 사용했을 때 <body>끝에 선언하는 것 보다 HTML을 파싱하는동안 JS를 다운로드 받을 수 있어 다운로드 받는 시간을 절약할 수 있다는 장점이 있다. 하지만 HTML전체가 파싱되기 전에 JS가 실행되기 때문에 JS에서 DOM 요소를 조작하려 했을 때 해당 요소가 아직 정의되지 않은 상태일 수 있다. 또, JS를 실행하는 동안엔 파싱이 중단되어 페이지가 멈출 수 있기 때문에 사용자가 페이지를 보는 데 시간이 걸릴 수 있다.
<!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를 만나면,
main.js파일을 병렬로 다운로드 하자고 명령만 해두고 다시 HTML을 끝까지 파싱하다가 main.js를 실행한다.
defer 옵션의 장점은 여러가지의 js파일을 다운로드 받아도 파싱 이후에 순서대로 js파일을 실행한다는 것이다.

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