Skip to content

Commit 0edf8f7

Browse files
[defer, async 스크립트] 보완
1 parent d9d18d8 commit 0edf8f7

File tree

1 file changed

+40
-50
lines changed

1 file changed

+40
-50
lines changed
Lines changed: 40 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,25 @@
11

2-
# 비동기, 지연 스크립트
2+
# defer, async 스크립트
33

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

6-
브라우저는 HTML을 읽다가 `<script>...</script>` 태그를 만나면 스크립트를 먼저 실행해야 하므로 DOM 생성을 멈춥니다. 이는 외부 스크립트 `<script src="..."></script>`를 만났을 때도 마찬가지입니다. 외부에서 스크립트를 다운로드받고 실행한 후에야 나머지 페이지들을 처리할 수 있습니다.
6+
브라우저는 HTML을 읽다가 `<script>...</script>` 태그를 만나면 스크립트를 먼저 실행해야 하므로 DOM 생성을 멈춥니다. 이는 `src` 속성이 있는 외부 스크립트 `<script src="..."></script>`를 만났을 때도 마찬가지입니다. 외부에서 스크립트를 다운받고 실행한 후에야 남은 페이지를 처리할 수 있습니다.
77

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

10-
1. 스크립트는 스크립트 아래에 있는 DOM 요소를 볼 수 없습니다. 따라서 핸들러 추가 같은 여러 행위가 불가능해집니다.
11-
2. 페이지 위쪽에 용량이 큰 스크립트가 있는 경우, 이 스크립트가 페이지를 '막아버립니다'. 페이지에 접속하는 사용자들은 스크립트를 다운받고 실행할 때까지 스크립트 아래쪽 페이지를 볼 수 없게 됩니다.
10+
1. 스크립트는에서 스크립트 아래에 있는 DOM 요소에 접근할 수 없습니다. 따라서 DOM 요소에 핸들러를 추가하는 것과 같은 여러 행위가 불가능해집니다.
11+
2. 페이지 위쪽에 용량이 큰 스크립트가 있는 경우 스크립트가 페이지를 '막아버립니다'. 페이지에 접속하는 사용자들은 스크립트를 다운받고 실행할 때까지 스크립트 아래쪽 페이지를 볼 수 없게 됩니다.
1212

1313
```html run height=100
1414
<p>...스크립트 앞 콘텐츠...</p>
1515

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

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

22-
이런 부작용들을 피할 수 있는 몇 가지 방법이 있습니다. 아래 예시처럼 스크립트를 페이지 맨 아래 놓는 것이 하나의 방법이 될 수 있죠. 이렇게 하면 스크립트에서 스크립트 위에 있는 요소를 볼 수 있습니다. 또한, 페이지 콘텐츠 출력을 막지 않게 됩니다.
22+
이런 부작용들을 피할 수 있는 몇 가지 방법이 있습니다. 아래 예시처럼 스크립트를 페이지 맨 아래 놓는 것이 하나의 방법이 될 수 있죠. 이렇게 하면 스크립트 위에 있는 요소에 접근할 수 있습니다. 또한, 페이지 콘텐츠 출력을 막지 않게 됩니다.
2323

2424
```html
2525
<body>
@@ -37,7 +37,7 @@
3737

3838
## defer
3939

40-
브라우저는 `defer` 속성이 있는 스크립트(이하 `defer` 스크립트 또는 지연 스크립트)를 '백그라운드'에서 불러옵니다. 실행은 페이지 로딩이 끝날 때까지 *지연* 되죠. `defer` 스크립트를 만나도 HTML 파싱이 멈추지 않는 이유는 여기에 있습니다.
40+
브라우저는 `defer` 속성이 있는 스크립트(이하 defer 스크립트 또는 지연 스크립트)를 '백그라운드'에서 다운로드 합니다. 따라서 지연 스크립트를 다운로드 하는 도중에도 HTML 파싱이 멈추지 않습니다. 그리고 `defer` 스크립트 실행은 페이지 구성이 끝날 때까지 *지연* 됩니다.
4141

4242
위쪽 예시와 동일한 코드인데 스크립트에 `defer`만 붙여보겠습니다.
4343

@@ -50,16 +50,16 @@
5050
<p>...스크립트 뒤 콘텐츠...</p>
5151
```
5252

53-
- 지연 스크립트는 HTML 파싱을 절대 막지 않습니다.
54-
- 지연 스크립트는 DOM 트리가 완성된 후, `DOMContentLoaded` 이벤트가 발생하기 전에 실행됩니다.
53+
- 지연 스크립트는 페이지 생성을 절대 막지 않습니다.
54+
- 지연 스크립트는 DOM이 준비된 후에 실행되긴 하지만 `DOMContentLoaded` 이벤트 발생 전에 실행됩니다.
5555

5656
예시를 통해 직접 살펴봅시다.
5757

5858
```html run height=100
5959
<p>...스크립트 앞 콘텐츠...</p>
6060

6161
<script>
62-
document.addEventListener('DOMContentLoaded', () => alert("DOM이 준비되고 `defer` 스크립트가 실행된 후, DOMContentLoaded 이벤트가 발생했습니다!")); // (2)
62+
document.addEventListener('DOMContentLoaded', () => alert("`defer` 스크립트가 실행된 후, DOM이 준비되었습니다!")); // (2)
6363
</script>
6464

6565
<script defer src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script>
@@ -68,21 +68,21 @@
6868
```
6969

7070
1. 페이지 콘텐츠는 바로 출력됩니다.
71-
2. `DOMContentLoaded` 이벤트는 지연 스크립트를 기다립니다. 따라서 `DOMContentLoaded` 이벤트는 DOM 트리가 완성되고 지연 스크립트가 실행된 후에 실행됩니다.
71+
2. `DOMContentLoaded` 이벤트는 지연 스크립트 실행을 기다립니다. 따라서 얼럿창은 DOM 트리가 완성되고 지연 스크립트가 실행된 후에 뜹니다.
7272

73-
지연 스크립트도 일반 스크립트와 마찬가지로 HTML에 추가된 순(상대순, 요소순)으로 실행됩니다.
73+
지연 스크립트는 일반 스크립트와 마찬가지로 HTML에 추가된 순(상대순, 요소순)으로 실행됩니다.
7474

75-
따라서 길이가 긴 스크립트가 앞에, 길이가 짧은 스크립트가 뒤에 있을 때, 뒤에 있는 스크립트는 앞의 스크립트가 실행될 때까지 대기합니다.
75+
따라서 길이가 긴 스크립트가 앞에, 길이가 짧은 스크립트가 뒤에 있어도 짧은 스크립트는 스크립트가 실행될 때까지 기다립니다.
7676

7777
```html
7878
<script defer src="https://javascript.info/article/script-async-defer/long.js"></script>
7979
<script defer src="https://javascript.info/article/script-async-defer/small.js"></script>
8080
```
8181

8282
```smart header="작은 스크립트는 먼저 다운되지만, 실행은 나중에 됩니다."
83-
브라우저는 성능을 위해 페이지에 어떤 스크립트들이 있는지 스캔한 다음 스크립트를 병렬적으로 다운로드합니다. 따라서 위 예시의 스크립트 다운도 병렬적으로 진행되죠. `small.js` 다운로드가 `long.js`보다 빨리 끝날 수 있습니다.
83+
브라우저는 성능을 위해 페이지에 어떤 스크립트들이 있는지 쭉 살펴본 후에야 스크립트를 병렬적으로 다운로드합니다. 위 예시에서도 스크립트 다운로드가 병렬적으로 진행되었습니다. 그런데 이 때 크기가 작은 `small.js``long.js`보다 먼저 다운로드 될 수 있습니다.
8484
85-
그렇지만 명세서에서 스크립트를 문서에 추가된 순서대로 실행하라고 정의했기 때문에 `small.js`는 `long.js` 다음에 실행됩니다.
85+
하지만 명세서에서 스크립트를 문서에 추가한 순서대로 실행하라고 정의했기 때문에 `small.js`는 `long.js` 다음에 실행됩니다.
8686
```
8787

8888
```smart header="`defer` 속성은 외부 스크립트에만 유효합니다."
@@ -92,16 +92,16 @@
9292
9393
## async
9494
95-
`async` 속성은 스크립트가 완전히 독립적으로 행동할 수 있게 보장해 줍니다.
95+
`async` 속성이 붙은 스크립트(이하 async 스크립트 또는 비동기 스크립트)는 페이지와 완전히 독립적으로 동작합니다.
9696
97-
- 페이지는 `async` 속성이 붙은 스크립트(이하 async 스크립트 또는 비동기 스크립트)를 기다리지 않고 페이지 내 콘텐츠를 처리, 출력합니다.
98-
- `DOMContentLoaded` async 스크립트는 서로를 기다리지 않습니다.
99-
- 페이지 구성이 끝나기 전에 `async` 스크립트 로딩이 끝난 경우, `DOMContentLoaded`는 async 스크립트 전에 발생할 수 있습니다,
100-
- async 스크립트가 짧거나 HTTP 캐시 안에 있는 경우, `DOMContentLoaded`는 `async` 스크립트 후에도 발생할 있습니다.
97+
- async 스크립트는 defer 스크립트와 마찬가지로 백그라운드에서 다운로드됩니다. 따라서 HTML 페이지는 async 스크립트 다운이 완료되길 기다리지 않고 페이지 내 콘텐츠를 처리, 출력합니다(하지만 async 스크립트 실행중에는 HTML 파싱이 멈춥니다 - 옮긴이).
98+
- `DOMContentLoaded` 이벤트와 async 스크립트는 서로를 기다리지 않습니다.
99+
- 페이지 구성이 끝난 후에 async 스크립트 다운로딩이 끝난 경우, `DOMContentLoaded`는 async 스크립트 실행 전에 발생할 수 있습니다,
100+
- async 스크립트가 짧아서 페이지 구성이 끝나기 전에 다운로드 되거나 스크립트가 캐싱처리 된 경우, `DOMContentLoaded`는 `async` 스크립트 실행 후에 발생할 수도 있습니다.
101101
- 다른 스크립트들은 `async` 스크립트를 기다리지 않습니다. `async` 스크립트 역시 다른 스크립트들을 기다리지 않습니다.
102102
103103
104-
이런 특징 때문에 페이지에 `async` 스크립트가 여러 개 있는 경우, 그 실행 순서가 제각각이 됩니다. 먼저 로드되는 스크립트가 먼저 실행됩니다.
104+
이런 특징 때문에 페이지에 `async` 스크립트가 여러 개 있는 경우, 그 실행 순서가 제각각이 됩니다. 실행은 다운로드가 끝난 스크립트 순으로 진행됩니다.
105105
106106
```html run height=100
107107
<p>...스크립트 앞 콘텐츠...</p>
@@ -116,11 +116,11 @@
116116
<p>...스크립트 뒤 콘텐츠...</p>
117117
```
118118

119-
1. 페이지 콘텐츠는 바로 출력됩니다. 비동기 스크립트는 페이지 로딩을 막지 않습니다.
120-
2. `DOMContentLoaded` 이벤트는 비동기 스크립트 전이나 후에 실행됩니다. 순서에 보장이 없습니다.
121-
3. 비동기 스크립트는 서로를 기다리지 않습니다. 위치상으론 `small.js`가 아래이긴 하지만 `long.js`보다 먼저 로드되었기 때문에 먼저 실행되었습니다. 이를 'load-first order'라고 부릅니다.
119+
1. 비동기 스크립트 다운로드는 페이지 로딩을 막지 않기 때문에 페이지 콘텐츠가 바로 출력됩니다.
120+
2. `DOMContentLoaded` 이벤트는 상황에 따라 비동기 스크립트 전이나 후에 실행됩니다. 정확한 순서를 예측할 수 없습니다.
121+
3. 비동기 스크립트는 서로를 기다리지 않습니다. 위치상으론 `small.js`가 아래이긴 하지만 `long.js`보다 먼저 다운로드되었기 때문에 먼저 실행됩니다. 이렇게 먼저 로드가 된 스크립트가 먼저 실행되는 것을 'load-first order'라고 부릅니다.
122122

123-
비동기 스크립트는 방문자 수 카운터나 광고 관련 스크립트같이 각각 독립적인 역할을 하는 서드 파티 스크립트를 현재 개발 중인 스크립트에 통합하려 할 때 아주 유용합니다. async 스크립트는 개발 중인 스크립트에 의존하지 않고, 그 반대도 마찬가지이기 때문입니다.
123+
비동기 스크립트는 방문자 수 카운터나 광고 관련 스크립트처럼 각각 독립적인 역할을 하는 서드 파티 스크립트를 현재 개발 중인 스크립트에 통합하려 할 때 아주 유용합니다. async 스크립트는 개발 중인 스크립트에 의존하지 않고, 그 반대도 마찬가지이기 때문입니다.
124124

125125
```html
126126
<!-- Google Analytics는 일반적으로 다음과 같이 삽입합니다. -->
@@ -130,42 +130,32 @@
130130

131131
## 동적 스크립트
132132

133-
자바스크립트를 사용하면 스크립트를 동적으로 추가할 수 있습니다(dynamic script).
133+
자바스크립트를 사용하면 문서에 스크립트를 동적으로 추가할 수 있습니다. 이렇게 추가한 스크립트를 동적 스크립트(dynamic script)라고 부릅니다.
134134

135135
```js run
136136
let script = document.createElement('script');
137137
script.src = "/article/script-async-defer/long.js";
138138
document.body.append(script); // (*)
139139
```
140140

141-
이렇게 추가한 동적 스크립트는 문서에 추가되자 마자(`(*)`로 표시한 줄) 스크립트 로딩이 시작됩니다.
141+
위 예시에서 외부 스크립트는 관련 요소가 문서에 추가되자 마자(`(*)`로 표시한 줄) 다운로드가 시작됩니다.
142142

143-
**동적 스크립트는 기본적으로 'async' 스크립트처럼 행동합니다.**
143+
그런데 **동적 스크립트는 기본적으로 'async' 스크립트처럼 행동합니다.**
144144

145-
따라서, 동적 스크립트는 다음과 같은 특징을 갖습니다.
146-
- 그 어떤 것도 기다리지 않습니다. 다른 자원들도 동적 스크립트를 기다리지 않습니다.
147-
- 먼저 로드된 스크립트가 먼저 실행됩니다('load-first order').
145+
따라서 다음과 같은 특징을 갖습니다.
146+
- 동적 스크립트는 그 어떤 것도 기다리지 않습니다. 그리고 그 어떤 것도 동적 스크립트를 기다리지 않습니다.
147+
- 먼저 다운로드된 스크립트가 먼저 실행됩니다('load-first' order).
148148

149-
150-
```js run
151-
let script = document.createElement('script');
152-
script.src = "/article/script-async-defer/long.js";
153-
154-
*!*
155-
script.async = false;
156-
*/!*
157-
158-
document.body.append(script);
159-
```
160-
161-
아래 예시에선 두 개의 스크립트를 동적으로 문서에 추가합니다. `script.async=false`가 없었다면 이 두 스크립트는 'load-first order'로 실행됩니다. 크기가 작은 `small.js`가 먼저 실행되겠죠. 하지만 `script.async=false`가 있기 때문에 실행은 '문서에 추가된 순서'대로 됩니다.
149+
아래 예시에선 두 스크립트를 동적으로 문서에 추가합니다. 그런데 `script.async=false`가 없었다면 이 스크립트들은 'load-first order'로 실행됩니다. 그럼 크기가 작은 `small.js`가 먼저 실행되겠죠. 하지만 `script.async=false`가 있기 때문에 실행은 '문서에 추가된 순서'대로 됩니다.
162150

163151

164152
```js run
165153
function loadScript(src) {
166154
let script = document.createElement('script');
167155
script.src = src;
156+
*!*
168157
script.async = false;
158+
*/!*
169159
document.body.append(script);
170160
}
171161

@@ -177,21 +167,21 @@ loadScript("/article/script-async-defer/small.js");
177167

178168
## 요약
179169

180-
`async``defer` 스크립트는 다운로드 시 페이지 렌더링을 막지 않는다는 공통점이 있습니다. 따라서 사용자는 오래 기다리지 않고도 페이지 콘텐츠를 볼 수 있습니다.
170+
`async``defer` 스크립트는 다운로드 시 페이지 렌더링을 막지 않는다는 공통점이 있습니다. 따라서 async와 defer를 적절히 사용하면 사용자가 오래 기다리지 않고 페이지 콘텐츠를 볼 수 있게 할 수 있습니다.
181171

182172
두 스크립트의 차이점은 다음과 같습니다.
183173

184-
| | 순서 | `DOMContentLoaded` |
174+
| | 순서 | DOMContentLoaded |
185175
|---------|---------|---------|
186-
| `async` | *load-first order*. 문서 내 순서와 상관없이 먼저 로드된 스크립트가 먼저 실행됩니다. | 비동기 스크립트는 문서가 완전히 다운로드되지 않은 상태라도 로드 및 실행될 수 있습니다. 스크립트 크기가 작거나 캐싱 처리 되어있을 때 혹은 문서 길이가 아주 길 때 이런 일이 발생합니다. |
187-
| `defer` | *문서에 추가된 순* | 지연 스크립트는 문서 로드와 파싱이 완료된 후에, `DOMContentLoaded` 이벤트 발생 전에 실행됩니다. |
176+
| `async` | *load-first order*. 문서 내 순서와 상관없이 먼저 다운로드된 스크립트가 먼저 실행됩니다. | 비동기 스크립트는 HTML 문서가 완전히 다운로드되지 않은 상태라도 로드 및 실행될 수 있습니다. 스크립트 크기가 작거나 캐싱 처리 되어있을 때 혹은 HTML 문서 길이가 아주 길 때 이런 일이 발생합니다. |
177+
| `defer` | *문서에 추가된 순* | 지연 스크립트는 문서 다운로드와 파싱이 완료된 후에, DOMContentLoaded 이벤트 발생 전에 실행됩니다. |
188178

189-
```warn header="스크립트 로딩이 안 되었어도 페이지는 동작해야 합니다."
179+
```warn header="스크립트 다운로드가 끝나지 않았어도 페이지는 동작해야 합니다."
190180
`defer`를 사용하게 되면 스크립트가 실행되기 *전* 에 페이지가 화면에 출력된다는 점에 항상 유의해야 합니다.
191181
192182
사용자는 그래픽 관련 컴포넌트들이 준비되지 않은 상태에서 화면을 보게 될 수 있죠.
193183
194-
따라서 지연 스크립트가 영향을 주는 영역엔 반드시 '로딩 인디케이터'가 있어야 합니다. 관련 버튼도 사용 불가(disabled) 처리를 해줘야 하죠. 이렇게 해야 사용자에게 현재 어떤 것은 사용할 수 있는지, 어떤 것은 사용할 수 없는지를 알려줄 수 있습니다.
184+
따라서 지연 스크립트가 영향을 주는 영역엔 반드시 '로딩 인디케이터'가 있어야 합니다. 관련 버튼도 사용 불가(disabled) 처리를 해줘야 하죠. 이렇게 해야 사용자에게 현재 어떤 것은 사용할 수 있는지, 어떤 것은 사용할 수 없는지를 알려줄 수 있습니다.
195185
```
196186

197187
실무에선 `defer`를 DOM 전체가 필요한 스크립트나 실행 순서가 중요한 경우에 적용합니다. `async`는 방문자 수 카운터나 광고 관련 스크립트같이 독립적인 스크립트에 혹은 실행 순서가 중요하지 않은 경우에 적용합니다.

0 commit comments

Comments
 (0)