자바스크립트 script async/defer 어트리뷰트
미음제
·2022. 10. 21. 22:05
async/defer 어트리뷰트
브라우저 렌더링 동작에 대해 공부하다 async/defer 어트리뷰트를 처음 접하게 되었고, 생소한 부분이라 다시 따로 공부한 것을 정리했다.
async/defer 어트리뷰트의 등장 배경
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<title>Documents</title>
<script>
const $animals = document.querySelector(".animals");
$animals.style.color = "blue";
</script>
</head>
<body>
<div class="animals">Cats</div>
</body>
</html>
위와 같은 html이 있다고 가정해보자. 브라우저 렌더링 동작 방식에 의해 위에서부터 아래로 차례로 내려오며 DOM과 CSSOM을 그린다. head tag 내부에서 script tag를 만나면, DOM 생성 과정을 중단하고 자바스크립트 엔진이 브라우저 렌더링 엔진에게서 제어권을 넘겨받고 자바스크립트 파싱과 실행을 한다.
script 내부를 보면, 'animals'라는 class를 갖는 요소에 접근하고, 해당 요소의 style 프로퍼티의 color 속성을 'blue'로 변경하는 DOM API 조작이 있다. DOM을 생성하는 과정에서 head 부분에서 script tag를 만나 DOM 생성이 중단되었고, 아직 생성되지 않은 DOM(class='animals'인 div)을 조작하려 했기 때문에 자바스크립트 실행 과정에서 에러가 발생하게 된다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<title>Documents</title>
</head>
<body>
<div class="animals">Cats</div>
<script>
const $animals = document.querySelector(".animals");
$animals.style.color = "blue";
</script>
</body>
</html>
이와 같은 에러를 방지하기 위해 DOM이 모두 생성되고 DOM을 조작하는 script tag를 body의 하단부에 선언함으로 이 문제를 해결할 수 있다. DOM이 완성되지 않은 시점에서 DOM을 조작하는 문제를 피할 수 있고, 자바스크립트가 실행되기 이전에 DOM 생성이 완료되어 렌더링 되기 때문에 페이지 로딩 시간도 단축할 수 있다.
그러나 근본적인 해결책은 아니다. 만약 HTML 파일이 말도 안 되게 크다고 생각해보면, script를 로드하고 실행하는 데까지 지연 시간이 길어지게 된다. 이 같은 문제를 해결하고자 HTML5부터 등장한 것이 async/defer 어트리뷰트이다.
async/defer 동작
우선 async/defer 어트리뷰트는 src 어트리뷰트로 외부 자바스크립트 파일을 로드하는 경우에만 사용할 수 있다. 앞선 예와 같이 in-line으로 script를 작성하는 경우에는 사용할 수 없다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<title>Documents</title>
</head>
<body>
<div class="animals">Cats</div>
<script async src="./aysnc.js"></script>
<script defer src="./defer.js"></script>
</body>
</html>
src 어트리뷰트로 script를 불러오는 경우 위와 같이 async/defer 어트리뷰트를 추가해 사용하면 된다. async/defer는 모두 HTML 파싱과 자바스크립트 로드가 비동기적으로 동작하게 해주는 키워드이다.
async
async는 HTML 파싱과 자바스크립트 로드를 비동기적으로 실행시켜 준다. 자바스크립트 파싱/실행 동작은 자바스크립트 파일이 모두 로드가 되면 실행한다(이때 HTML 파싱 동작은 중단).
<div>script 이전</div>
<script async src='index1.js'></script>
<script async src='index2.js'></script>
<div>script 이후</div>
async 어트리뷰트는 javascript 로드 과정을 비동기적으로 처리할 수 있게만 도와준다. 자바스크립트 파일이 로드가 완료되면 바로 해당 자바스크립트를 실행하고 이때 HTML파싱(DOM 생성)이 잠시 중단된다.
위에 작성한 것처럼 script 순서를 적었더라도 순서가 보장되지는 않는다.
<div>script 이전</div>
<script async src='index1.js'></script> <!--로드 시간 10초-->
<script async src='index2.js'></script> <!--로드 시간 1초-->
<div>script 이후</div>
가령 위처럼 'index2' 자바스크립트가 로드되는데 1초가 걸린다면, 해당 자바스크립트 파일이 먼저 로드가 되고 실행된다. 따라서 실행 순서가 의존적인 스크립트라면 순서가 보장되지 않는 async는 피하는 것이 좋다.
또한, 스크립트를 비동기적으로 부르기 때문에 DOM이 모두 생성된 후 발생하는 DOMContentLoaded 이벤트 실행을 보장할 수 없다.
<!-- Google Analytics는 일반적으로 다음과 같이 삽입합니다. -->
<script async src="https://google-analytics.com/analytics.js"></script>
DOM을 직접 조작하거나 의존성이 발생하지 않는 독립적인 서드파티에 접근할 때 사용하기 유용하다.
defer
defer 역시 async와 마찬가지로 HTML 파싱과 자바스크립트 로드를 비동기적으로 실행시켜준다. 차이점은 자바스크립트의 실행 시점이다. defer의 경우 HTML이 파싱이 완료되고 난 후에 자바스크립트가 실행된다.
<div>script 이전</div>
<script async src='index1.js'></script> <!--로드 시간 10초-->
<script async src='index2.js'></script> <!--로드 시간 1초-->
<div>script 이후</div>
async의 경우 실행 순서가 보장되지 않았지만, defer는 입력된 순서대로 실행 순서가 보장된다.
DOM 트리가 완성된 후 자바스크립트가 실행되기 때문에 DOM 조작이 가능하고, 순서를 보장하기 때문에 의존적인 스크립트를 사용해야 하는 경우 적합하다고 할 수 있다.
DOMContentLoaded
DOMContentLoaded : DOM 트리가 완성된 직후(그러나 img와 같은 외부 소스가 로드되지 않은 시점) 발생
onload : 모든 소스(img, css, scripts, ...)가 로드된 직후 발생
이 내용은 '모던 자바스크립트 Deep Dive' 책을 보고 공부한 내용을 다시 정리한 것입니다.
참고
'Developer > TI' 카테고리의 다른 글
Babel, Webpack(바벨, 웹팩) (0) | 2022.11.22 |
---|---|
프로미스, 콜백 함수 (0) | 2022.11.08 |
이벤트 버블링/캡쳐링, 이벤트 위임 (1) | 2022.10.30 |
브라우저 렌더링 과정 정리 (2) | 2022.10.11 |
REST API, RESTful API 정리 (0) | 2022.09.29 |