看完这两篇:
- 【对浏览器首次渲染时间点的探究】https://github.com/jin5354/404forest/issues/73
- 【深入探究 eventloop 与浏览器渲染的时序问题】https://www.404forest.com/2017/07/18/how-javascript-actually-works-eventloop-and-uirendering/
- 【浏览器的工作原理:新式网络浏览器幕后揭秘】https://www.html5rocks.com/zh/tutorials/internals/howbrowserswork/
结论:
如果在 HTML 解析过程中,解析到了某个脚本,但这个脚本被 CSS 阻塞住了或者还没下载完,则会中断暂存当前的解析 task,继续执行 eventloop,(参考下方给出的event loop的图) 网页被渲染。
如果 JS 全部是内联的,或者网速好,在解析到</script>时脚本全都已下载完了,则解析 task 不会被中断,也就不会出现渲染情况了。
这种特性可以用来做页面骨架屏(解析中断时提早渲染页面)
先上一段代码
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>demo</title>
</head>
<body>
<div id="app">
<p>俺是用来测试首屏渲染的文字。</p>
</div>
<script src="./bundle.js"></script>
</body>
</html>
-
问
<p>俺是用来测试首屏渲染的文字。<p>
会渲染吗 - 在脚本被阻塞的时候,是会直接渲染出来的
『需要着重指出的是,这是一个渐进的过程。为达到更好的用户体验,呈现引擎会力求尽快将内容显示在屏幕上。它不必等到整个 HTML 文档解析完毕之后,就会开始构建呈现树和设置布局。在不断接收和处理来自网络的其余内容的同时,呈现引擎会将部分内容解析并显示出来。』 - 来自『浏览器的工作原理:新式网络浏览器幕后揭秘』https://www.html5rocks.com/zh/tutorials/internals/howbrowserswork/
所以:html解析完毕之前,是可以进行绘制的,那么上述
<p>
标签能渲染出来吗?
首先 PerformancePaintTiming API
Paint Timing 提供的
PerformancePaintTiming
是一个提供页面在构建过程中的“绘制”(也称“渲染”)时间点信息的接口。“绘制”是指将渲染树转换为页面上像素的过程。(可以获取页面绘制过程中相关事件(FP、FCP、FMP、TTI等)发生的时间)
----------------它的规范中说明:如果update the rendering
实例是first-paint (FP 首屏加载衡量指标之一)
那么就记录时间戳,上报为first-paint
时间。如果update the rendering
实例是first-contentful-paint
那么就记录时间戳,上报为first-contentful-paint (FCP 指标之一)
时间。
什么是update the rendering
?
-
先上图
可以看到是event loop的最后一个阶段
所以: 浏览器的首次渲染,只是老老实实的按照eventloop
来运行而已。eventloop
第一次进行到update the rendering
阶段的时间点那就是first-paint
的时间点了
eventloop
是怎么运行的?
eventloop
按照task > microtask > render
的顺序执行,所以可以看到,微队列是先于页面渲染运行的。对于task来说,Html解析其实就是一种典型的task
在 html parser
规范中检索 eventloop
得:
(原文很晦涩,这里为了方便理解,直接翻译最核心的几句:)
当解析到
</script>
时:如果当前文档存在阻碍 JS 执行的 CSS 或者当前的脚本 不处于
ready to be parser-executed
状态,spin the event loop,直到不再存在阻碍 JS 执行的 CSS 且该段脚本处于ready to be parser-executed
。
我们已经知道 CSS 的加载是会阻碍 JS 执行的。而脚本不处于这个 ready to be parser-executed
状态简单理解就是还没下载完。如果出现这两种情况,脚本就无法立刻执行,需要等待。此时要进行 spin the eventloop,查阅规范,该操作即为:
(简单翻译)
- 暂存此时正在执行的 task 或 microtask
- 暂存此时的 js 执行上下文堆栈
- 清空 js执行上下文堆栈
- 如果当前正在执行的是 task,执行 microtask checkpoint
- 停止执行当前的 task/microtask。继续执行 eventloop 的主流程。
- 当满足条件时,重新添加之前暂存的 task/microtask,恢复暂存的 js 执行上下文堆栈,继续执行。
简单的说就是让 eventloop
中断并暂存当前正在执行的 task/microtask,保持 eventloop
的继续执行,待一段时间之后满足条件了再恢复之前的 task/microtask。
那么问题就水落石出了:
如果在 HTML 解析过程中,『解析到了某个脚本,但这个脚本被 CSS 阻塞住了或者还没下载完』,则会中断暂存当前的解析
task
,继续执行eventloop
,网页被渲染。
如果 JS 全部是内联的,或者网速好,在解析到
</script>
时脚本全都已下载完了,则解析 task 不会被中断,也就不会出现渲染情况了。