页面渲染的过程

一、页面渲染是客户端请求页面的最后一步

  1. DNS解析域名
  2. TCP建立连接
  3. HTTP发送请求
  4. 服务器返回状态码及相应数据
  5. 浏览器解析渲染页面

 

二、页面渲染

浏览器加载,解析,渲染页面

  解析html 构建dom树 -> 构建render树 -> 布局render树 -> 绘制render树 :

页面渲染的过程

我们知道浏览器为了体验友好,并不是文档全部都解析才绘制到屏幕上,而是从上至下开始解析html,遇到css 会开启线程下载css;

解析:

  1. 浏览器会将HTML解析成一个DOM树(Document Object Model 文档对象模型),DOM树的构建过程是一个深度遍历过程,当前节点的所有子节点都构建好后才会去构建当前节点的下一个兄弟节点,
  2. 将CSS解析成CSS规则树(或CSSOM树,CSS Object Model CSS对象模型);
  3. 构建:根据DOM树和CSS来构造render树,渲染树不等于DOM树,像header和display:none;这种没有具体内容的东西就不在渲染树中;
  4. 布局:根据render树,浏览器可以计算出网页中有哪些节点,各节点的CSS以及从属关系,然后可以计算出每个节点在屏幕中的位置;
  5. 绘制:遍历render树进行绘制页面中的各元素。

 

性能优化中重绘、重排:

  1. Reflow(回流/重排):当它发现了某个部分发生了变化影响了布局,渲染树需要重新计算。
  2. Repaint(重绘):改变了某个元素的背景颜色,文字颜色等,不影响元素周围或内部布局的属性,将只会引起浏览器的repaint,根据元素的新属性重新绘制,使元素呈现新的外观。重绘不会带来重新布局,并不一定伴随重排;
  • Reflow要比Repaint更花费时间,也就更影响性能。所以在写代码的时候,要尽量避免过多的Reflow。

 

reflow的原因:

  1. 页面初始化的时候;
  2. 操作DOM时;
  3. 某些元素的尺寸变了;
  4. 如果 CSS 的属性发生变化了。

 

减少 reflow/repaint

  1. 不要一条一条地修改 DOM 的样式。与其这样,还不如预先定义好 css 的 class,然后修改 DOM 的 className。
  2. 不要把 DOM 结点的属性值放在一个循环里当成循环里的变量。
  3. 为动画的 HTML 元件使用 fixed 或 absoult 的 position,那么修改他们的 CSS 是不会 reflow 的。
  4. 千万不要使用 table 布局。因为可能很小的一个小改动会造成整个 table 的重新布局。

 

这里主要注意浏览器渲染进程(多线程)中的GUI(图形用户界面)线程JS引擎线程。  浏览器渲染进程详细内容

  • GUI(图形用户界面)线程
    • 负责渲染浏览器界面,解析HTML、CSS、构建DOM树和RenderObject树,布局和绘制等
    • 当界面需要重绘,重排时,该线程就会执行
    • GUI渲染线程和JS引擎线程是互斥的,当js引擎执行时GUI线程会被挂起(相当于冻结),GUI更新会被保存在一个队列中等到js引擎空闲时立即执行
  • JS引擎线程
    • 也称为js内核,负责处理javascript脚本程序(V8引擎)
    • JS引擎线程负责解析javascript脚本,运行代码
    • js脚本运行的代码会按代码的执行顺序依次被添加到一个执行队列中(定时器除外),按先进先出的原则执行,一个浏览器渲染进程中无论如何都只有一个js线程在运行js程序。通常所说的js是单线程就是这块的内容
    • 由于GUI渲染线程和JS引擎线程是互斥的,所以如果JS执行时间如果过长,这样就会造成页面渲染不连贯,导致页面渲染加载阻塞

 

三、影响页面渲染的因素

1、css注意事项

  1. dom深度尽量浅。
  2. 减少inline javascript、css的数量。
  3. 避免使用通配符,

 

2、javascript 注意事项:

  1. 如果在解析html的时候遇到js会阻塞页面渲染,所以一般将script标签放到页面底部,也就是body闭合标签之前,这能确保在脚本执行前页面已经完成了DOM树渲染。
  2. 尽可能地合并脚本。页面中的script标签越少,加载也就越快,响应也越迅速。无论是外链脚本还是内嵌脚本都是如此。

采用无阻塞下载 JavaScript 脚本的方法:
(1)使用script标签的 defer、async 属性、;
(2)使用动态创建的script元素来下载并执行代码等异步加载等方法;

 

defer、async 区别:

  • defer、async都是异步下载,但是执行时刻不一致;
  • 相同点:
    • 加载文件时不阻塞页面渲染;
    • 使用这两个属性的脚本中不能调用document.write方法;
    • 允许不定义属性值,仅仅使用属性名;
  • 不同点:
    • html的版本html4.0中定义了defer,html5.0中定义了async;这将造成由于浏览器版本的不同而对其支持的程度不同;
    • 每一个async属性的脚本都在它下载结束之后立刻执行,同时会在window的load事件之前执行,所以就有可能出现脚本执行顺序被打乱 的情况;
    • 每一个defer属性的脚本都是在页面解析完毕之后,按照原本的顺序执行,同时会在document的DOMContentLoaded之前执行;

 

 

最后来一个加载渲染的过程:

  1. 用户输入网址(假设是第一次访问),浏览器向服务器发出请求,服务器返回html文件;
  2. 浏览器开始载入html代码,发现<head>标签内有一个<link>标签引用外部CSS文件;
  3. 浏览器又发出CSS文件的请求,服务器返回这个CSS文件;
  4. 浏览器继续载入html中<body>部分的代码,并且CSS文件已经拿到手了;
  5. 浏览器在代码中发现一个<img>标签引用了一张图片,向服务器发出请求。此时浏览器不会等到图片下载完,而是继续加载后面的代码;
  6. 服务器返回图片文件,由于图片占用了一定面积,影响了后面段落的排布,因此浏览器需要回过头来重新渲染这部分代码;
  7. 浏览器发现了一个包含一行Javascript代码的<script>标签,直接运行该脚本;
  8. Javascript脚本执行了这条语句,它命令浏览器隐藏掉代码中的某个<div> (style.display=”none”)。少了一个元素,浏览器不得不重新渲染这部分代码;
  9. </html>表示暂时加载完成;
  10. 此时用户点了一下界面中的“换肤”按钮,Javascript让浏览器换了一下<link>标签的CSS路径;
  11. 浏览器向服务器请求了新的CSS文件,重新加载页面。然后执行渲染过程。
     

 

 

再回顾一下 浏览器渲染页面 流程图 

页面渲染的过程