# 浏览器渲染相关

# 渲染

浏览器如何渲染页面:

浏览器的网络线程获取到HTML文档后,会生成一个渲染任务,交给渲染主线程的消息队列,通过事件循环开启渲染流程。

# 1) 解析 HTML - Parse HTML

HTML 通过 Parse HTML 生成两颗树(DOM树CSSOM树),从而开放JS操作的能力。

解析过程: 浏览器会提前开启预解析线程,会下载外部的CSSJS文件,同时也会解析CSS

  1. 遇到 CSS 解析 CSS,遇到 JS 执行 JS。
  2. 如果解析到link,主线程不会等待,会继续解析后面的HTML,所以CSS不会阻塞HTML的解析,解析CSS的任务交给预解析线程An image
  3. 如果解析到script,会停止解析HTML,等待JS完全下载完成后并且等待全局代码解析完成后,才能继续解析HTML(因为 JS 可能会修改当前的 DOM 树)。 An image
  4. 最终生成 DOM 树和 CSSOM 树。

# 2) 样式计算

主线程会遍历整个DOM树,依次计算出每个节点的最终样式,生成一个带有样式的DOM树

relow: 会更改CSSOM树,本质就是重新计算layout树,需要重新计算布局树,会引发 layout 以及后续的步骤。浏览器内部会对多次计算layout进行优化,改动属性造成的reflow是异步完成的,但是读取属性(document.clientWidth)会导致立即reflow

# 3) 布局 - Layout

依次遍历DOM树的每个节点,生成Layout树,包含每个节点的宽高,相对包含块的位置等。

DOM树的区别:有些属性display:none会显示在DOM树,而在布局树中不显示。类似的还有伪元素等。

An image

# 4) 分层 - Layer

渲染主线程会用一套浮躁的策略对整个Layout树进行分层,分层的好处在于,将来某一个层改变后,仅会对该层进行后续处理,提示效率。

滚动条,层叠上下文,transform,opacity 等样式都会影响分层结果,也可以使用will-change属性更大程度影响分层结果。

# 5) 绘制 - Paint

渲染主线程会为每个层单独产生绘制指令集,用于描述这一层的内容该如何画出来。 这个时候主线程结束。

repaint: 和几何信息无关,比如color:red,会重新根据分层新计算绘制指令。

# 6) 分块 - Tiling

合成线程会从线程池中取得一些分块线程,对每个图层进行分块,将其划分成更多的小区域。

# 7) 光栅化 - Raster

GPU进程处理,最终将每个块变成位图,优先处理靠近视口的块,这个过程会用到GPU加速

# 8) 画 - Draw

最后一步就是画,合成线程拿到每个层,每个块的位图后,生成一个个指引(quad)信息。 指引会标识出每个位图应该画到屏幕的哪个位置,以及会考虑旋转,缩放等变形。 变形发生在合成线程,和渲染主线程无关,这就是transform效率高的本质原因。 合成线程把指引交给 GPU 进程,产生系统调用,交给 GPU 硬件,完成最终的屏幕成像。

# document.write

Chrome 下:

<body style="background-color:white;">
  <div>header</div>
  <script>
    document.write("<p>body</p>");
  </script>
  <div>footer</div>
</body>

浏览器在解析的时候,遇到了script标签,则会执行,将p里面的内容渲染到文档流,也就是footer的前面,之后会浏览器会继续解析文档流,渲染p标签的内容,最终的展示元素:

 <div>header</div>
  <script>
    document.write('<p>body</p>')
  </script><p>body</p>
  <div>footer</div>

如果在脚本中加入setTimeout,页面中只会有body内容,其余的都会被清空,究其原因是文档流在这个时候已经关闭,当执行document.write的时候会重新调用document.open打开新的文档流,document.open会清楚已有文档,所以会清除之前的内容。同理DOMContentLoadedonload事件也会出现一样的情况。

<body style="background-color:white;">
  <div>header</div>
  <script>
    setTimeout(() => {
      document.write("<p>body</p>");
    });
  </script>
  <div>footer</div>
</body>

比如:

document.writeln('
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.js"></script>
'); window.onload = function() { console.log($); };

这个时候就会生成script标签,并且在加载完成之后触发执行console.log

总结:所以如果要用document.write进行载入(不是重写),需要在onload之前执行。在已经加载完的页面中调用document.write会重写页面。