为什么脚本在浏览器显示页面之前完成

Wat*_*lla 5 javascript

在下面的代码中,为什么console.log在显示任何HTML元素之前完成循环?我已将JavaScript代码放在HTML文件的末尾.

<!DOCTYPE html>
<html lang="en">

<body>

    <p id="counter"> no clicks yet </p>
    <script>
        for (i = 0; i < 99999; ++i) {
            console.log(i);
        }
        console.log("ready to react to your clicks");
    </script>
</body>

</html>
Run Code Online (Sandbox Code Playgroud)

更新:

根据一个答案,我尝试了这个,只有在完全执行控制台日志循环后才能显示HTML文档:

<html lang="en">

<body onload="onLoad()">
    <button onclick="clickHandler()">Click me</button>
    <p id="counter"> no clicks yet </p>
    <script>
        var counter = 0;
        function clickHandler() {
            counter++;
            document.getElementById("counter").innerHTML = "number of clicks:" + counter;
        }
        function onLoad() {
            for (i = 0; i < 99999; ++i) {
                console.log(i);
            }
            console.log("ready to react to your clicks");
        }

    </script>
</body>

</html>
Run Code Online (Sandbox Code Playgroud)

Lou*_*uis 8

你问的是不同的问题.你的标题问:

为什么在生成DOM模型之前执行console.log?

我所理解的"在生成DOM模型之前"是"在p元素创建之前".该p元素是你之前创建的script元素运行时,它是从它访问.如果你document.getElementById("counter")在脚本中,你会得到你的段落.

然后你问:

为什么console.log的循环在显示任何HTML元素之前完成?

这是一个不同的问题.这里的问题不是解析已经停止(与Simeon Stoykov建议的相反).确实解析停止了,但它没有解释为什么段落没有显示.浏览器可以script执行时显示段落.(实际上,如果你在script元素中放置一个断点,Chrome会在断点被击中后立即显示该段落.)

发生的事情是浏览器正在应用优化.浏览器的延迟尽可能可能回流(计算元素的页面上的位置和几何形状)和元素的渲染.目标是减少回流和渲染所花费的总时间.假设您没有将数字转储到控制台的脚本,而是使用一个脚本来更改段落的样式,从而使段落更改位置或大小.一个天真的浏览器可能会这样做:

  1. 立即回流并渲染p#counter.
  2. 执行脚本,更新样式,以便p#counter更改旧位置和大小.
  3. 回流和渲染p#counter以反映更改.

像FF或Chrome这样的真实浏览器会跳过上面的第一步,只会重排和渲染p#counter 一次而不是两次.

在像你这样的简单例子中,优化并不是很好,但想象一个复杂的页面,其中包含一堆表和一个启动脚本,可以立即用数据,图像,链接等行填充表格.能够减少X的回流次数- 渲染操作,只需一次回流渲染即可产生巨大的差异.

在您给出的示例中,浏览器知道在脚本执行时用户无法对页面执行任何操作,因此在脚本完成之前,浏览器中没有任何内容可以呈现页面.

此时出现了一个问题:

如果浏览器延迟计算元素的位置和大小,那么为什么我可以document.getElementById("counter").getBoundingClientRect()在我的脚本中执行操作并获取坐标

浏览器可以尽可能地延迟它.当JavaScript代码查询元素的位置或大小时,就不再可能延迟.浏览器必须在那里进行回流,然后给出答案.(在我上面的讨论中,我已经谈到了重排并一起渲染以简化一点.但是回流并不一定会立即跟随渲染.因此渲染可能仍然会延迟到脚本执行完毕之后.)


如果您的示例意图表示阻止渲染并阻止您的用户获得任何反馈的长计算,那么解决这个问题的方法就是将冗长的工作分解为块:执行大量工作,然后用于setTimeout安排下一个块,依此类推,直到整个工作完成.例如:

const p = document.getElementById("counter");
let i = 0;
const limit = 100;
const chunkSize = 10;
function doWork() {
  for (let thisChunk = 0; thisChunk < chunkSize && i < limit; thisChunk++) {
    console.log(i++);
    p.textContent = i;
  }
  if (i < limit) {
    setTimeout(doWork, 0);
  }
}
doWork();
Run Code Online (Sandbox Code Playgroud)

在上面的示例中,执行了10个数字的块,然后setTimeout调度下一个块,脚本将控制权返回给JavaScript事件循环,后者执行所需的任务.这包括响应用户交互,从而将页面呈现给用户.

在某些情况下,将整个工作卸载到一个中可能是有意义的WebWorker.


小智 0

当浏览器在解析 HTML 时看到 script 标签时,它会停止解析并开​​始运行脚本。解析器在执行脚本之前不会继续。这是默认行为。这就是为什么您的脚本在您可以看到呈现的页面之前就被执行的原因。如果您希望在页面加载和呈现后执行脚本,您可以使用不同的技术,例如:

<script defer>
Run Code Online (Sandbox Code Playgroud)

或者

<body onload="functionToBeExecutedAfterPageLoads();">
Run Code Online (Sandbox Code Playgroud)

或者

document.onload = function ...
Run Code Online (Sandbox Code Playgroud)