Script 태그
DOM이 생성될 때, Script 태그를 만나면 DOM 생성을 멈추고 Script를 실행한다.
src 속성을 포함한 외부 스크립트를 만났을 때에도 마찬가지이다.
해당 스크립트를 다운 받고 실행할 때 까지 Script 태그 아래에 선언된 DOM 요소들은 대기해야 한다.
여기서 DOM 생성을 중단하지 않고, 스크립트를 동시에 내려받게 할 수 있는 방법으로
defer, async가 있다.
DOM 생성 중단이 일어난다면 어떤 문제가 발생하는가.
- 위에서 언급한 대로, 스크립트 태그 아래에 있는 DOM 요소는 대기하므로, 해당 DOM 요소들에 접근할 수 없다.
- querySelector와 같이 DOM을 직접적으로 조작하여 핸들러등을 추가하는 등의 행위가 불가능하다.
- 스크립트의 용량이 크다면, 페이지 'Block' 현상이 일어난다.
- 스크립트 실행 전까지 사용자는 스크립트 아래의 콘텐츠를 볼 수 없으며, 페이지를 정상적으로 이용할 수도 없다.
보통의 경우에서는 다음과 같은 지연이 눈에 잘 띄지 않으나
네트워크 환경이 열악한 지역 혹은 모바일 네트워크 등등에서는 문제가 될 가능성이 크다.
예시를 통해 확인해보자.
<!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>
<p>스크립트 태그 이전</p>
<script src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script>
<p>스크립트 태그 이후</p>
</body>
</html>
script를 다운로드 / 실행할 때, script 태그 아래의 콘텐츠는 보이지 않는 것을 알 수 있다.
normal, defer, async 스크립트의 동작에 대한 이해
normal, defer, async의 차이에 대한 이해를 도울만한 시각적인 자료가 있어서 가져왔다.
먼저 그림을 통해 각 속성별 동작의 차이를 느끼고,
다음에 나오는 각 속성별로 정리한 내용을 통해 좀 더 자세한 내용을 파악해보자.
defer 속성
defer 속성은 스크립트를 '백그라운드'에서 다운로드한다.
그러므로 HTML 파싱(DOM 생성)을 중단시키지 않으며 defer 속성이 있는 스크립트의 다운로드가 끝나더라도
DOM 생성을 마칠 때 까지 지연된 후 스크립트를 실행한다.
유의할 점으로는 DOM 준비 이후 바로 스크립트를 실행하며, 이 시점은 DOMContentLoaded 이벤트가 발생하기 전이다.
그리고 defer 스크립트는 일반 스크립트와 마찬가지로,
순서를 지키며 실행되므로 앞선 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>
<p>스크립트 태그 이전</p>
<script>
document.addEventListener('DOMContentLoaded', () =>alert('defer 스크립트가 실행된 후에, DOMContentLoaded 이벤트가 발생시 실행할 콜백이 실행된다.!'));
</script>
<script defer src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script>
<!--무거운 스크립트-->
<script defer src="https://javascript.info/article/script-async-defer/small.js"></script>
<!--가벼운 스크립트-->
<p>스크립트 태그 이후</p>
</body>
</html>
요약하면 다음과 같다.
- 페이지의 콘텐츠는 DOM을 파싱하며 바로 출력된다.
- defer 속성이 담긴 script는 백그라운드에서 다운로드되며, DOM 생성이 완료될 때 까지 지연된다.
- DOM 생성이 완료되고, DOMContentLoaded 이벤트가 발생하기 전에 script가 순서대로 실행된다.
- defer에 의해 지연되던 script가 모두 실행되면, DOMContentLoaded 이벤트가 발생한다.
- defer 속성은 외부 스크립트(src 속성을 이용하는 스크립트)에만 유효하므로, src 속성이 없다면 defer를 이용할 수 없다.
async 속성
async 속성은 페이지 작업과 완전히 독립적으로 동작한다.
defer와 마찬가지로, async 속성을 통하면 스크립트는 백그라운드에서 다운로드된다.
그러나 스크립트는 다운로드 즉시 실행되며, 스크립트 실행 도중에는 DOM 생성을 중단한다.
다운로드 즉시 실행되기 때문에, DOMContentLoaded 이벤트 이전의 실행을 보장하지 않는다.
심지어는 DOMContentLoaded 이후에 스크립트가 실행될 수도 있다.
마지막으로 async는 '다운로드 완료 순서'에 의존하므로,
defer가 선언된 순서를 지키며 스크립트를 실행 했던 것과는 달리, async는 선언된 순서와 실행 순서가 무관하다.
이를 'load first order' 라고 부른다.
async 스크립트를 비동기 스크립트라고 하는데, 이는 각각 스크립트가 독립적인 역할을 하는 광고 등의 서드파티 스크립트 등을
현재 개발중인 스크립트에 통합할 때 유용하다고 한다.
>> async 스크립트는 다른 스크립트에 의존하지 않는 독립성을 보장하기 때문이다.
async 스크립트를 실행해보면,
<!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>
<p>스크립트 태그 이전</p>
<script>
document.addEventListener('DOMContentLoaded', () => alert('DOMContentLoaded!'));
</script>
<script async src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script>
<!-- 무거운 스크립트 -->
<script defer src="https://javascript.info/article/script-async-defer/small.js"></script>
<!-- 가벼운 스크립트 -->
<p>스크립트 태그 이후</p>
</body>
</html>
DOMContentLoaded -> Small Script -> Long Script 의 순으로 실행되었다.
이를 요약하면 다음과 같다.
- 페이지의 콘텐츠는 DOM을 파싱하며 바로 출력된다.
- asnyc 속성이 담긴 script는 백그라운드에서 다운로드되며, 다운로드 즉시 실행되어 실행 도중에는 DOM 파싱을 중단한다.
- DOMContentLoaded 이벤트는 async 스크립트가 실행되기 이전에도 실행될 수 있다. = 정확한 순서 예측이 불가능하다.
- async 스크립트는 서로를 기다리지 않으므로, HTML에서 선언 순서와 실행 순서는 무관하다.
정리
async, defer 스크립트는 다운로드 시 페이지 렌더링(DOM 생성)을 막지 않는다.
그래서 적절한 async, defer 사용은 사용자의 UX를 향상시킬 수 있다.
defer 속성은 보통 DOM 전체가 필요한 스크립트나 실행 순서가 중요할 때(B 스크립트는 A 스크립트 이후에 실행되어야 할 때) 적용한다.
async 속성은 방문자 수 카운터 혹은 광고 스크립트 등과 같이 독립적으로 작동하는 스크립트에 적용한다.
이와 같이 DOM 파싱을 막지 않는 옵션을 사용할 때는, 페이지가 출력 되었으나, 스크립트가 실행되지 않은 시점을 조심해야 한다.
그러므로, 로딩 스피너를 사용하고 비즈니스 로직을 처리해야 하는 버튼등을 비활성화 시키는 등의 추가 조치를 통해
사용자에게 아직 페이지가 완전히 불러와지지 않았다는 것을 시각적으로 보여줄 수 있어야 한다.
참고 사이트
'HTML & CSS' 카테고리의 다른 글
[CSS] box-sizing : border-box; (0) | 2022.08.30 |
---|