我试图通过一个简单的例子来理解defer。我有下面的JS代码,需要5秒钟才能运行。
home.js
function wait(ms){
var start = new Date().getTime();
var end = start;
while(end < start + ms) {
end = new Date().getTime();
}
}
wait(5000);
我正在 home.html 中使用它,如下所示:
<html>
<head>
<script defer src="home.js"></script>
</head>
<body>
<h1>Hello World!</h1>
</body>
</html>
我发现,如果使用defer或将我的脚本放在body标签的末尾(据我所知,这是等效的),则hello world仅在5秒后出现。但是如果我使用asnyc,“ hello world”会立即出现。为什么会这样?
答案 0 :(得分:1)
要回答您的迫切问题,有关MDN的Script Element文档将有所帮助。
推迟:
具有defer属性的脚本将阻止DOMContentLoaded 从触发到脚本加载完毕并完成评估为止的事件。
异步:
这是一个布尔属性,指示浏览器应 可能,异步加载脚本。
异步脚本不会阻止DOMContentLoaded事件触发。
有几种更好的方法可以使dom内容在延迟后出现,这些延迟不会像您所做的那样阻塞UI线程。这是非常糟糕的做法。
摘录自DOMContentLoaded:
同步JavaScript暂停DOM的解析。如果您想要DOM 在用户请求后尽快解析 页面,您可以使JavaScript异步
通常的做法是在文档加载后添加dom内容。您仍然可以通过超时来执行此操作,但是通常会在其他asynchronous action之后加载其他dom内容,例如从fetch收到的响应。
以下是延迟后添加dom内容的示例:
function wait(ms){
window.setTimeout(function() {
var element = document.createElement("h1")
element.innerText = "Hello World!"
document.body.appendChild(element)
}, ms)
}
wait(5000);
<html>
<head>
<script src="home.js"></script>
</head>
<body>
</body>
</html>
答案 1 :(得分:1)
让我们看一下(打开调试控制台以查看输出):
test.js
document.addEventListener("DOMContentLoaded", function(event) {
console.log("[4]: 'DOMContentLoaded' event fired!");
});
function wait(ms){
console.log("[2]: 'wait' started!");
var start = new Date().getTime();
var end = start;
while(end < start + ms) {
end = new Date().getTime();
}
console.log("[3]: 'wait' finished!");
}
wait(5000);
test.html
<html>
<head>
<script defer src="test.js"></script>
</head>
<body>
<h1>Hello World!</h1>
<script>console.log('[1]: DOM is parsed!')</script>
</body>
</html>
控制台日志:
[1]: DOM is parsed! [2]: 'wait' started! [3]: 'wait' finished! [4]: 'DOMContentLoaded' event fired!
现在您可以看到以下步骤:
wait
函数(因为它是deferred
,并且在解析DOM之后但在触发DOMContentLoaded
事件之前运行)。DOMContentLoaded
事件被触发(仅在所有defer
脚本按照它们在HTML中的顺序执行之后)。在不同的浏览器中,DOM呈现时间可能有所不同,并且 托管HTML文件的方式:在Chrome(如我的测试所示)中,对于
http://
,DOM为 在运行defer
脚本之前呈现,但是在file:///
情况下 在运行defer
脚本并完成其同步代码后呈现DOM。
摘要(来自您评论中的问题)
defer
和async
脚本是异步加载的。defer
脚本将按照它们在HTML中的放置顺序执行。defer
脚本将在加载和解析DOM之后但在触发DOMContentLoaded
之前执行。async
脚本可以随时加载并以任何顺序运行(最有可能按照加载顺序)。如果在解析DOM之前恰好加载了这样的脚本,则不能指望该脚本能够找到HTML中的某些元素。另外,在这种情况下,浏览器不会等待脚本加载并执行以触发DOMContentLoaded
事件。答案 2 :(得分:1)
@Sergey的回答很好,但并没有真正解释。 4.12.1 here下的示意图显示,当指定defer
时,浏览器将等待脚本执行开始,直到整个HTML被解析 。关键是解析不呈现。渲染与延期脚本的执行同时 开始,但是在这种特殊情况下,脚本陷入了沉重的循环。如果wait()
函数未阻塞,则渲染将有机会在解析完成后和并行执行 后立即使用非阻塞的延迟脚本执行来有效地更新DOM。脚本完成后,仍然会触发DOMContentLoaded。
function wait(ms){
return new Promise((resolve, reject) => {setTimeout(() => resolve(), ms)});
}
wait(3000).then(() => console.log('done'));
<html>
<head>
<script defer src="home.js"></script>
</head>
<body>
<h1>Hello World!</h1>
</body>
</html>
答案 3 :(得分:0)
我不同意您的观察结论... defer
不会使页面慢,如以下动画所示。请注意,页面开始渲染而无需等待DOM内容加载事件:
因此,您可以自由使用defer
或async
depending on your requirements。
但是,根据页面是通过网络加载还是从文件系统加载,Chrome似乎在执行JavaScript之前或之后开始绘制页面:
网络:
文件系统:
虽然我不确定为什么Chrome选择采用这种方式,但我只能说这是您无法控制或依赖的。 defer
旨在优化JavaScript内容在网络上的传递,其中大部分不适用于文件系统。