3

我试图用一个简单的例子来理解延迟。我有下面的 JS 代码,它需要 5 秒才能运行。

主页.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 或将我的脚本放在正文标签的末尾(在我的理解中两者是等效的),hello world 仅在 5 秒后出现。但是如果我使用 asnyc 'hello world' 会立即出现。为什么是这样?

4

3 回答 3

1

让我们看看这个(打开调试控制台查看输出):

测试.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);

测试.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被解析!
[2]:“等待”开始!
[3]:“等待”结束!
[4]: 'DOMContentLoaded' 事件被触发!

现在你可以看到这些步骤发生了:

  1. DOM 被加载和解析。
  2. wait函数运行(因为它deferred在解析 DOM 之后但在DOMContentLoaded事件触发之前运行)。
  3. 'defer' 脚本完成。
  4. DOMContentLoaded事件被触发(只有在所有defer脚本都按照它们在 HTML 中的顺序执行之后)。
  5. HTML 已呈现,但某些资源(如样式表和图像)仍可加载。

DOM 渲染时间在不同的浏览器和托管 HTML 文件的方式中可能不同:在 Chrome 中(如我的测试所示),如果http://DOM 在defer脚本运行之前渲染,但如果file:///DOM 在defer脚本运行后渲染他们的同步代码就完成了。

摘要(来自您在评论中的问题):

  • defer并且async脚本是异步加载的。
  • defer脚本将按照它们在 HTML 中的放置顺序执行。
  • defer脚本将在 DOM 加载和解析之后执行,但在DOMContentLoaded触发之前。
  • async脚本可以随时加载并以任何顺序运行(很可能按加载顺序)。如果在解析 DOM 之前恰好加载了这样的脚本,则不能指望该脚本能够找到 HTML 中的某些元素。此外,在这种情况下,浏览器不会等待脚本加载并执行来触发DOMContentLoaded事件。
于 2019-01-31T10:39:22.590 回答
1

要回答您的直接问题,MDN 上的Script Element文档会有所帮助。

推迟:

具有 defer 属性的脚本将阻止 DOMContentLoaded 事件触发,直到脚本加载并完成评估。

异步:

这是一个布尔属性,指示浏览器应该(如果可能)异步加载脚本。

异步脚本不会阻止 DOMContentLoaded 事件触发。

有几种更好的方法可以让 dom 内容在延迟后出现,不会像您正在做的那样阻塞 UI 线程;这是一个非常糟糕的做法。

来自DOMContentLoaded的摘录:

同步 JavaScript 暂停解析 DOM。如果您希望在用户请求页面后尽快解析 DOM,您可以使 JavaScript 异步

通常的做法是在加载文档后附加 dom 内容。您仍然可以通过超时来执行此操作,但通常会在其他一些异步操作(例如从fetch收到的响应)之后加载额外的 dom 内容。

这是延迟后附加 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>

于 2019-01-31T10:31:39.967 回答
1

@Sergey 的回答很好,但并没有真正解释。这里4.12.1下的示意图显示,当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>

于 2019-01-31T10:51:32.363 回答