1
1
2
- # Script 标签属性 :async, defer
2
+ # 脚本 :async, defer
3
3
4
4
现代的网站中,脚本往往比 HTML 更“重”:它们的大小通常更大,处理时间也更长。
5
5
6
6
当浏览器加载 HTML 时遇到 ` <script>...</script> ` 标签,浏览器就不能继续构建 DOM。它必须立刻执行此脚本。对于外部脚本 ` <script src="..."></script> ` 也是一样的:浏览器必须等脚本下载完,并执行结束,之后才能继续处理剩余的页面。
7
7
8
8
这会导致两个重要的问题:
9
9
10
- 1 . 脚本不能访问到位于它们下面的 DOM 元素,因此,脚本不能给它们添加事件等 。
11
- 2 . 如果页面顶部有一个庞大的脚本 ,它会“阻塞页面”。在脚本下载并执行结束前 ,用户都不能看到页面内容:
10
+ 1 . 脚本不能访问到位于它们下面的 DOM 元素,因此,脚本无法给它们添加处理程序等 。
11
+ 2 . 如果页面顶部有一个笨重的脚本 ,它会“阻塞页面”。在该脚本下载并执行结束前 ,用户都不能看到页面内容:
12
12
13
13
``` html run height=100
14
14
<p >...content before script...</p >
15
15
16
16
<script src =" https://javascript.info/article/script-async-defer/long.js?speed=1" ></script >
17
17
18
- <!-- 在脚本加载结束前都看不到下面的内容 -->
18
+ <!-- This isn't visible until the script loads -->
19
19
<p >...content after script...</p >
20
20
```
21
21
29
29
</body >
30
30
```
31
31
32
- 但是这样的方案绝非完美 。例如:浏览器只有在下载完整的 HTML 文档后才会注意到脚本(并且开始下载它 )。对于长的 HTML 文档来说,这样的延迟必须引起注意 。
32
+ 但是这种解决方案远非完美 。例如,浏览器只有在下载了完整的 HTML 文档之后才会注意到该脚本(并且可以开始下载它 )。对于长的 HTML 文档来说,这样可能会造成明显的延迟 。
33
33
34
- 对于网络连接很快的人来说 ,这不值一提。但是这个世界上仍然有很多地区的人们他们使用很慢的网络连接,并且使用着远非完美的移动互联网 。
34
+ 这对于使用高速连接的人来说 ,这不值一提,他们不会感受到这种延迟。但是这个世界上仍然有很多地区的人们所使用的网络速度很慢,并且使用的是远非完美的移动互联网连接 。
35
35
36
- 幸运的是,这里有两个 ` <script> ` 属性可以解决我们的这个问题 :` defer ` 和 ` async ` 。
36
+ 幸运的是,这里有两个 ` <script> ` 特性(attribute)可以为我们解决这个问题 :` defer ` 和 ` async ` 。
37
37
38
38
## defer
39
39
40
- ` defer ` 属性告诉浏览器它应该继续处理页面,并在“后台 ”下载脚本,然后等页面处理完成后才开始执行此脚本 。
40
+ ` defer ` 特性告诉浏览器它应该继续处理页面,并“在后台 ”下载脚本,然后等页面加载完成后,再执行此脚本 。
41
41
42
- 接下来的这个例子和上面一样,但是是用 ` defer ` 属性 :
42
+ 这是与上面那个相同的示例,但是带有 ` defer ` 特性 :
43
43
44
44
``` html run height=100
45
45
<p >...content before script...</p >
50
50
<p >...content after script...</p >
51
51
```
52
52
53
- - 具有 ` defer ` 属性的脚本不会阻塞页面的加载 。
54
- - 具有 ` defer ` 属性的脚本总是要等到 DOM 解析完毕,但在 ` DOMContentLoaded ` 事件之前执行。
53
+ - 具有 ` defer ` 特性的脚本不会阻塞页面 。
54
+ - 具有 ` defer ` 特性的脚本总是要等到 DOM 解析完毕,但在 ` DOMContentLoaded ` 事件之前执行。
55
55
56
- 下面的例子演示了这一过程 :
56
+ 下面这个示例演示了这一过程 :
57
57
58
58
``` html run height=100
59
59
<p >...content before scripts...</p >
68
68
```
69
69
70
70
1 . 页面内容立即显示。
71
- 2 . ` DOMContentLoaded ` 在等待 defer 脚本动作的完成。它仅在脚本 ` (2) ` 下载且执行结束后才被触发 。
71
+ 2 . ` DOMContentLoaded ` 等待具有 ` defer ` 特性的脚本执行完成。 ` DOMContentLoaded ` 仅在脚本 ` (2) ` 下载且执行结束后才会被触发 。
72
72
73
- Defer 脚本保持他们的相对顺序 ,就像常规脚本一样。
73
+ 具有 ` defer ` 特性的脚本保持其相对顺序 ,就像常规脚本一样。
74
74
75
- 所以 ,如果我们有一个长脚本在前,一个短脚本在后,那么后者就会等待前者。
75
+ 因此 ,如果我们有一个长脚本在前,一个短脚本在后,那么后者就会等待前者。
76
76
77
77
``` html
78
78
<script defer src =" https://javascript.info/article/script-async-defer/long.js" ></script >
79
79
<script defer src =" https://javascript.info/article/script-async-defer/small.js" ></script >
80
80
```
81
81
82
82
``` smart header="短脚本先下载完成,但是后执行"
83
- 浏览器解析页面找到 script 属性并并行下载它们, 以提高性能。因此,在上面的实例中,两个脚本并行下载 。`small.js` 可能会先下载完成。
83
+ 浏览器扫描页面寻找脚本,然后并行下载它们, 以提高性能。因此,在上面的示例中,两个脚本是并行下载的 。`small.js` 可能会先下载完成。
84
84
85
- 但是规范要求脚本按照文档顺序执行,因此它要等到 `long.js` 执行结束才会被执行。
85
+ 但是规范要求脚本按照文档顺序执行,因此,它需要等到 `long.js` 执行结束才会被执行。
86
86
```
87
87
88
- ```smart header="` defer ` 属性仅适用于外部脚本 "
89
- ` defer ` 属性会忽略没有 ` src ` 属性的 ` <script> ` 脚本 。
88
+ ```smart header="` defer ` 特性仅适用于外部脚本 "
89
+ 如果 ` <script> ` 脚本没有 ` src ` ,则会忽略 ` defer ` 特性 。
90
90
```
91
91
92
92
93
93
## async
94
94
95
- `async` 属性意味着脚本是完全独立的 :
95
+ `async` 特性意味着脚本是完全独立的 :
96
96
97
- - 页面不会等待异步脚本,它会继续处理页面并显示内容 。
98
- - `DOMContentLoaded` 和 async 脚本不会彼此等待 :
99
- - `DOMContentLoaded` 可能发生在异步脚本之前(此时异步脚本在页面加载完成后才加载完成 )
100
- - `DOMContentLoaded` 也可能发生在异步脚本之后(此时异步脚本可能很短或者是从 HTTP 缓存中加载的)
101
- - 其他脚本不会等待 `async` 脚本加载完成,同样 `async` 脚本也不会等待其他脚本。
97
+ - 页面不会等待异步脚本,它会继续处理并显示页面内容 。
98
+ - `DOMContentLoaded` 和异步脚本不会彼此等待 :
99
+ - `DOMContentLoaded` 可能会发生在异步脚本之前(如果异步脚本在页面完成后才加载完成 )
100
+ - `DOMContentLoaded` 也可能发生在异步脚本之后(如果异步脚本很短,或者是从 HTTP 缓存中加载的)
101
+ - 其他脚本不会等待 `async` 脚本加载完成,同样, `async` 脚本也不会等待其他脚本。
102
102
103
103
104
- 因此,如果我们有几个 `async` 脚本,它们可能按任意次序执行,总之是先加载完成的就先运行 :
104
+ 因此,如果我们有几个 `async` 脚本,它们可能按任意次序执行。总之是先加载完成的就先执行 :
105
105
106
106
```html run height=100
107
107
<p>...content before scripts...</p>
@@ -116,19 +116,19 @@ Defer 脚本保持他们的相对顺序,就像常规脚本一样。
116
116
<p>...content after scripts...</p>
117
117
```
118
118
119
- 1 . 页面内容立刻显示出来:` async ` 脚本不会阻塞页面加载 。
120
- 2 . ` DOMContentLoaded ` 可以在 ` async ` 之前或之后触发,不能保证谁在前谁在后 。
121
- 3 . Async 脚本不会等待彼此。一个小的脚本 ` small.js ` 放在后面,但是可能会比 ` long.js ` 这个长脚本先加载完成,所以尽管 ` small.js ` 在后面,但是它可能先运行。这一行为被称为 “加载优先(load-first) ”顺序。
119
+ 1 . 页面内容立刻显示出来:` async ` 脚本不会阻塞页面 。
120
+ 2 . ` DOMContentLoaded ` 可能在 ` async ` 之前或之后触发,不能保证谁先谁后 。
121
+ 3 . 异步脚本不会等待彼此。较小的脚本 ` small.js ` 排在第二位,但可能会比 ` long.js ` 这个长脚本先加载完成,所以 ` small.js ` 会先执行。这被称为 “加载优先”顺序。
122
122
123
- 当我们将独立的第三方脚本集成到页面的时候 ,此时采用异步加载方式是非常棒的:计数器,广告等等 ,因为它们不依赖于我们的脚本,同样我们的脚本也不应该等待它们加载完成 :
123
+ 当我们将独立的第三方脚本集成到页面时 ,此时采用异步加载方式是非常棒的:计数器,广告等 ,因为它们不依赖于我们的脚本,我们的脚本也不应该等待它们 :
124
124
125
125
``` html
126
126
<!-- Google Analytics 脚本通常是这样嵌入页面的 -->
127
127
<script async src =" https://google-analytics.com/analytics.js" ></script >
128
128
```
129
129
130
130
131
- ## 动态脚本(Dynamic scripts)
131
+ ## 动态脚本
132
132
133
133
我们也可以使用 JavaScript 动态地添加脚本:
134
134
@@ -138,15 +138,14 @@ script.src = "/article/script-async-defer/long.js";
138
138
document .body .append (script); // (*)
139
139
```
140
140
141
- 当脚本附加到文档 ` (*) ` 时,脚本就会开始加载:
141
+ 当脚本被附加到文档 ` (*) ` 时,脚本就会立即开始加载。
142
142
143
- ** 默认情况下,动态脚本表现为 “异步”行为 。**
143
+ ** 默认情况下,动态脚本的行为是 “异步”的 。**
144
144
145
- 这也就是说 :
146
- - 它们不会等待其他内容,其他的内容也不会等待它们 。
147
- - 先加载完成的脚本先运行 (“加载优先” 顺序)
145
+ 也就是说 :
146
+ - 它们不会等待任何东西,也没有什么东西会等它们 。
147
+ - 先加载完成的脚本先执行 (“加载优先”顺序)。
148
148
149
- 我们可以通过将 ` async ` 属性显示修改为 ` false ` 以将加载优先顺序修改为文档顺序(就像常规脚本一样):
150
149
151
150
``` js run
152
151
let script = document .createElement (' script' );
@@ -159,7 +158,9 @@ script.async = false;
159
158
document .body .append (script);
160
159
```
161
160
162
- 例如,这里我们添加了两个脚本。在没有设置 ` script.async=false ` 时,它们执行顺序为加载优先顺序(即 ` small.js ` 可能先运行)。但是当设置了 ` script.async=false ` 后,脚本执行顺序就是它在文档中的顺序:
161
+ 我们可以通过将 ` async ` 特性显式地修改为 ` false ` ,以将脚本的加载顺序更改为文档顺序(就像常规脚本一样):
162
+
163
+ 例如,这里我们添加了两个脚本。在没有设置 ` script.async=false ` 时,它们执行顺序为加载优先顺序(即 ` small.js ` 可能先执行)。但是当设置了 ` script.async=false ` 后,脚本执行顺序就变成了“脚本在文档中的顺序”:
163
164
164
165
165
166
``` js run
@@ -170,29 +171,29 @@ function loadScript(src) {
170
171
document .body .append (script);
171
172
}
172
173
173
- // 由于 async=false 属性存在, long.js 会先运行
174
+ // long.js 先执行,因为代码中设置了 async=false
174
175
loadScript (" /article/script-async-defer/long.js" );
175
176
loadScript (" /article/script-async-defer/small.js" );
176
177
```
177
178
178
179
179
180
## 总结
180
181
181
- ` async ` 和 ` defer ` 属性有一个共同点:它们都不会阻塞页面的渲染 。因此,用户可以立即阅读并了解页面内容。
182
+ ` async ` 和 ` defer ` 有一个共同点:加载这样的脚本都不会阻塞页面的渲染 。因此,用户可以立即阅读并了解页面内容。
182
183
183
- 但是它们之间也存在一些本质的区别 :
184
+ 但是,它们之间也存在一些本质的区别 :
184
185
185
- | 类型 | 顺序 | ` DOMContentLoaded ` |
186
+ | | 顺序 | ` DOMContentLoaded ` |
186
187
| ---------| ---------| ---------|
187
- | ` async ` | ** 加载优先顺序** 。脚本在文档中的顺序不重要 —— 先加载完成先运行 | 无关紧要。可能在文档还未完全下载前加载执行 。如果脚本很小或者来自于缓存,同时文档又足够长 ,就会发生这种情况。|
188
- | ` defer ` | ** 文档顺序** (它们在文档中的位置 ) | 在 ` DOMContentLoaded ` 之前且在文档加载解析之后执行(可能需要等待)。 |
188
+ | ` async ` | ** 加载优先顺序** 。脚本在文档中的顺序不重要 —— 先加载完成先执行 | 不相关。可能在文档加载完成前加载并执行完毕 。如果脚本很小或者来自于缓存,同时文档足够长 ,就会发生这种情况。 |
189
+ | ` defer ` | ** 文档顺序** (它们在文档中的顺序 ) | 在文档加载和解析完成之后(如果需要,则会等待),即在 ` DOMContentLoaded ` 之前执行。 |
189
190
190
191
``` warn header="没有脚本的页面应该也是可用的"
191
- 请注意,如果你使用的是 `defer`,那么在脚本加载之前页面都是 “可见”的 。
192
+ 请注意,如果你使用的是 `defer`,那么该页面在脚本加载之前就 “可见”。
192
193
193
- 因此,用户可以阅读这个页面内容,但是一些图形组件可能没有准备完成 。
194
+ 因此,用户可以阅读这个页面的内容,但是某些图形组件可能尚未准备好 。
194
195
195
- 所以,这就需要在页面适当位置添加“加载”进度指示,禁用无效的按钮,以清楚地向用户显示什么准备好了什么没有准备好 。
196
+ 所以,我们应该在页面适当位置添加“正在加载”的指示,并且被禁用的按钮也应该这样显示,这样用户就可以清晰地看到什么准备好了,什么还没准备好 。
196
197
```
197
198
198
- 在开发中,通常在脚本需要整个 DOM 文档或者脚本的相对执行顺序很重要的时候,使用 ` defer ` 属性。而当脚本之间互相独立,比如计数器或者广告,并且它们相对执行顺序不重要的时候,此时使用 ` async ` 属性 。
199
+ 在实际开发中, ` defer ` 用于需要整个 DOM 的脚本,和/或脚本的相对执行顺序很重要的时候。 ` async ` 用于独立脚本,例如计数器或广告,这些脚本的相对执行顺序无关紧要 。
0 commit comments