为啥浏览器在执行 JavaScript 之前并不总是完成对前面 HTML 的渲染?

Posted

技术标签:

【中文标题】为啥浏览器在执行 JavaScript 之前并不总是完成对前面 HTML 的渲染?【英文标题】:Why is a browser not always finishing rendering of the preceding HTML, before executing JavaScript?为什么浏览器在执行 JavaScript 之前并不总是完成对前面 HTML 的渲染? 【发布时间】:2018-04-06 11:04:21 【问题描述】:

问题是关于以下代码:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Test</title>
</head>
<body>
    One line of HTML code
    <script>
        // Synchronous delay of 5 seconds
        var timeWhile = new Date().getTime();
        while( new Date().getTime() - timeWhile < 5000 );
    </script>
</body>

我在 Firefox 和 Chrome 中对其进行了测试,它们在 5 秒后而不是在 5 秒内显示(渲染):“一行 HTML 代码”。为什么浏览器会这样做?

我理解为什么浏览器在执行 javascript 时必须停止渲染,因为您可以使用 JavaScript 更改元素的样式(例如)。如果浏览器必须同时显示和更改内容,就会出现问题。这就是浏览器在执行 JavaScript 时阻止渲染的原因。

在上面的例子中,当开始执行 JavaScript 时,“一行 HTML 代码”已经被“HTML 解析器”解析了。它必须如此,因为 JavaScript 可以包含例如 document.write,所以附加的字符串必须在前面的 HTML 之后。显然在“解析 HTML”和显示/呈现相同的 HTML 之间有一段时间,否则这个例子中的浏览器会在 5 秒内显示一些东西,但事实并非如此。

当你用大量的HTML代码替换“一行HTML代码”时,浏览器会在5秒内显示一些内容,所以原则上可以显示一些内容。

如果我是浏览器,我会这样做:

解析“一行html代码” 看到一些 JavaScript 块 在“JavaScript 块”之前完成 HTML 的渲染,因此浏览器将在此时显示:“一行 HTML 代码” 现在暂停渲染并执行 JavaScript 代码。 执行 JavaScript 代码后,再次开始渲染。

在这样的示例中,浏览器可以提前 5 秒显示一些内容。就渲染而言,这是一个很大的速度提升。

也许这是浏览器可以改进的地方,但也许还有另一个原因。也许有人对此了解更多并可以向我解释。

【问题讨论】:

奇怪的是,如果你在 JS 中设置一个断点,你会看到 一行 html 代码 在 5 秒之前显示。 @George 这并不是那么“奇怪”,因为调试器的定义是:“当调试器被调用时,执行在调试器语句处暂停。”他们正在谈论javascript执行。执行 javascript 时无法进行渲染,但我可以在没有执行时进行。所以当暂停执行时,html在javascript之前,可以再次渲染。 浏览器会解析 html,并且在脚本中可用。解析和渲染是有区别的。也可以说是脚本可以更改内容的大变化,这需要重新渲染页面。因此,在等待渲染时,就像浏览器现在所做的那样,速度会有所提高。显然,您的示例与现实生活相去甚远,在这种情况下,人们会尽可能地异步。我没有证据或规格链接,所以我将其作为评论留下。 @msoft 如果浏览器从缓存中获取脚本,异步将给出相同的结果。如果 html 在 Javascript 之前,例如不是“一行 html 代码”,而是“数千行 html 代码”,那么有可能一部分在“javascript 执行”之前已经呈现,而另一部分则没有。浏览器不会总是阻止第一部分的渲染,所以不是因为你建议的速度增益。否则浏览器将永远不会在“javascript 执行”之前显示任何 html,这是不正确的。 @msoft 由于一些 javascript 行,浏览器重新渲染页面是没有问题的。这对速度没有影响。浏览器不能同时渲染和“执行 javascript”(至少 Chrome 和 Firefox)的原因是 javascript 可以改变元素的样式。您不能在同一时刻显示和更改某些内容。这就是原因,而不是速度增益,因为没有。 【参考方案1】:

尝试将上面示例中的内联 javascript 外部化。

在内联脚本中,运行脚本会占用时间,这可能会改变 DOM。试图在 DOM 发生变异时渲染它会造成混乱。所以渲染只发生在 JS 停止的时候,因此 DOM 是稳定的。

在等待外部脚本下载时,脚本的运行会停止,因此可以安全地渲染 DOM。下载的 JS 在渲染完成之前不会运行。

希望对您有所帮助!

问候, 埃比

【讨论】:

谢谢,但它不是那样工作的。外部脚本也可以阻止渲染。特别是当内容来自缓存并且没有下载时间时。 如果您希望在 javascript 执行之前进行渲染,请尝试延迟加载 javascript,它仅在 DOM onload 事件触发后才被调用。例如,您可以将异步添加到外部化脚本中。 还有异步它不是那样的。很多人都误解了异步,所以你这样想我并不感到惊讶。 Javascript 总是阻塞渲染,如果前面的 html 还没有完成渲染,就会在 javascript 执行过程中被阻塞。异步对此没有影响。【参考方案2】:

解析和渲染是两种截然不同的操作,可以由浏览器独立运行,但都可以对HTML/CSS/etc代码的小sn-ps进行操作,不需要全部资源全部加载完毕即可开始做他们各自的工作。当然,任何被渲染的东西都必须首先被解析,但似乎解析不一定需要完全完成才能运行 JavaScript 代码,并且要尽快开始显示用户内容,这对于浏览器在解析完成之前开始渲染页面。

考虑对您的示例代码进行这种修改(我在 macOS 上的 Google Chrome 版本 62.0.3202.75 中对此进行了测试):

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>Test</title>
    </head>
    <body>
        One line of html code
        <script>
            // Synchronous delay of 5 seconds
            var timeWhile = new Date().getTime();
            for (var current = new Date().getTime(); current - timeWhile < 5000; current = new Date().getTime()) 
                if (current - timeWhile === 2500) 
                    alert(document.body.childNodes[0].nodeValue);
                    alert(document.body.childNodes[2].nodeValue);
                
            ;
        </script>
        Another line of HTML code
    </body>

我在您的代码中添加了 alert()s 而不是 console.log()s,因为同步延迟似乎也阻止了向 JavaScript 控制台刷新/写入。

第一个alert() 在页面上出现任何文本之前显示“一行 html 代码”字符串,证明页面的该部分在呈现之前已被解析。

但是,第二个alert() 不会发生。因为“另一行 HTML 代码”行还没有被解析,它没有被定义为 document.body 的子节点,所以试图访问它会抛出一个错误,阻止显示警报,而是显示在JavaScript 控制台:Uncaught TypeError: Cannot read property 'nodeValue' of undefined

如果您在页面加载后在控制台中手动重新运行alert(document.body.childNodes[2].nodeValue);,您会看到如预期的“另一行 HTML 代码”的警报。

我不确定为什么在同步延迟发生之前页面上没有呈现“一行 html 代码”字符串,但我认为该行为特定于浏览器的实现。

【讨论】:

第 1 部分:我现在正在阅读您的文章,并且我过去也像您一样进行过测试,但您的测试实际上并没有说明什么。知道“一行 html 代码”在 DOM 中,但还没有出现在屏幕上就足够了。我的示例显示相同。其余的对于本次讨论是不必要的。所以这实际上是没有答案的问题;)。 第 2 部分:还对您所说的内容进行了更正:“但似乎解析不一定需要完全完成才能运行 JavaScript 代码”。 javascript 执行(同步)之前的 HTML 必须始终位于 DOM 中,在开始执行之前(因此必须完成 html 解析)。前面的 html 的呈现不一定需要完全完成才能运行 JavaScript 代码。但千万不要混淆“渲染”和“html解析”。 @MaartenBruins 我不确定您的原始示例如何显示“一行 html 代码”在 DOM 中而不是屏幕中,这就是我的示例所做的。原始示例中没有任何内容是通过 JavaScript 访问 DOM。我不相信我混淆了渲染和解析,我想说一个页面的单个 sn-ps 可以被解析,然后每个 sn-p 独立渲染。 你说得对,在我的这个例子中我没有访问 DOM,因为问题不在于那部分。我没有用我的测试证明这一点,但是从理论上(和早期的测试)我知道在同步环境中,在开始执行代码之前,html 必须位于 DOM 中。我的测试更多是关于在开始执行时是否还不能完成“渲染”。 @segfault但我不知道你是不是错了,但要回答这个问题,你是说:“但似乎 JavaScript 的解析不一定需要完全完成要运行的代码”(示例是关于同步环境)“解析”到底是什么意思。哪个解析器? html 解析器?

以上是关于为啥浏览器在执行 JavaScript 之前并不总是完成对前面 HTML 的渲染?的主要内容,如果未能解决你的问题,请参考以下文章

为啥以前在 iPad 上缓存时,SVG 中的图像并不总是从应用程序缓存中加载?

HTTPS并不总像它看起来那么可靠

为啥 Maven 不总是从 theartefactory 下载最后一个依赖项,从竹子构建?

为啥我可以在 JavaScript 中定义函数之前使用它?

为啥 === 在 JavaScript 中比 == 快? [关闭]

为啥 JavaScript Intellisense 在 Visual Studio 2012 中并不总是有效?