浏览器的解析渲染原理以及JS、CSS阻塞问题分析

浏览器的解析渲染原理以及JS、CSS阻塞问题分析

输入Url地址按下回车发生了什么?

1:根据网址进行DNS解析,将相应的域名解析为IP地址
2:客户端根据IP地址去寻找对应的服务器并进行TCP三次握手,建立TCP连接
3:客户端发起HTTP请求,请求对应资源
4:服务器响应并返回相应数据(如:HTML文件)
5:浏览器将获取的HTML文档由HTML解析器解析成DOM树
6:同时由CSS解析器将CSS样式解析成CSS Rule Tree(CSS规则树)
7:将生成的DOM树和CSS规则树合并生成Rendering Tree(渲染树)
8:根据渲染树,在屏幕上对元素进行布局
9:根据渲染树,将各个元素绘制到屏幕上
10:客户端与服务器进行TCP的四次挥手

浏览器的解析渲染原理如下图过程所示:

浏览器的解析渲染原理以及JS、CSS阻塞问题分析_第1张图片

浏览器内核(渲染进程)拿到静态资源后,渲染大概可以划分成以下几个步骤:

  • 解析html构建dom树
  • 解析css构建render树(将CSS代码解析成树形的数据结构,然后结合DOM合并成render树)
  • 布局render树(Layout/reflow),负责各元素尺寸、位置的计算
  • 绘制render树(paint),绘制页面像素信息
  • 浏览器会将各层的信息发送给GPU,GPU会将各层合成(composite),显示在屏幕上。

从以上步骤可以得出几个结论:

1.html和css的加载和解析是异步的,不会互相干扰
2.只有等dom树和cssom树构建完成并且合并成render树之后,页面才开始渲染,所以css的加载不会影响dom的解析,但会影响dom的渲染

浏览器渲染网页注意事项

1:DOM的构建过程是一个深度遍历的过程,即当前节点的所有子节点都构建好以后才会去构建当前节点的下一个兄弟节点

2:渲染树和DOM不同,因为像DOM树中的display:none元素不会出现在渲染树中,但visibility: hidden会出现在渲染树中

3:DOM解析和CSS解析是同时进行,互不影响

4:渲染树的生成并不是等到DOM树和CSS规则树解析完成才开始合并,而是边解析、边合并渲染

浏览器渲染网页阻塞顺序

1:构建DOM树时,如遇到JS元素时,会阻塞DOM树和CSS规则树的构建,优先执行JS文件

2:构建DOM树时,如遇到CSS元素时,会开启异步请求线程,该线程会先下载CSS文件,再构建CSS规则树,该线程会阻塞JavaScript引擎线程,但不会阻塞DOM树的构建

<head>
  <link rel="stylesheet" href="./style.css?sleep=3000"> //假设css的加载会花3s
  <script src="./index.js">script>
head>
<body>
  <p>hello worldp>
body>

为啥说css的加载会阻塞js引擎线程呢? 万万不要认为是css的加载阻塞了DOM的解析!

流程是:css和js可以同时去加载—>css加载很慢,即使js已经加载完了也会等到css加载完了才开始执行—>js一直不执行所以阻塞了DOM的解析,因为上面说了js是会阻塞DOM的解析的,浏览器会等到js执行了才会继续往下解析DOM。

那么为什么js明明都加载完了,还要等css加载完才开始执行呢?

可以这样理解,浏览器并不知道js中的代码会干些什么,js可以去改动DOM,也可以获取/改变css样式。js要获取正确的样式就必须等css加载完

3:CSS解析和JS解析互斥,也就是说JS解析时会阻塞CSS解析而CSS解析时也会阻塞JS解析

4:JS解析时,如果JS还操作了CSS,而这个CSS还没有下载或构建解析,则会延迟执行JS,直到完成CSS下载构建解析,再会继续执行JS

白话说明

浏览器在渲染网页时会开启两条线程,渲染引擎线程和JS引擎线程,但这两条线程是互斥的,同时只能有一个线程在执行。也就是说在构建DOM树时,渲染引擎在执行:

当遇到JS时:渲染引擎会停止执行,控制权交给JS引擎,当执行JS代码时
如果遇到获取DOM,那么如果该DOM还没有解析,则会获取为null,浏览器在解析时,如果遇到了script标签,会先渲染一次这个script标签之前的DOM,然后再去加载和执行js。 如果script上面还有 css 没记载完,先去加载上面css。 加载完成后再去执行js代码?见上述例子如果JS代码还操作了CSS,而这个CSS如果还没有下载和构建,那么浏览器首先会阻塞JS引擎执行,然后会开启一个异步请求线程,在该线程上,去下载构建CSS规则树,CSS规则树构建完成后,再继续执行JS代码,当JS执行完以后,控制权再次交给渲染引擎去执行。

当遇到CSS元素时:也会开启异步线程,去下载构建CSS规则树,但同时也会继续构建后面的DOM树,也就是说DOM解析和CSS解析可以同时进行,但如果后面遇到JS元素(见上述例子),则会阻塞JS引擎线程执行,后面DOM树解析不受影响。

关于script的async/defer属性

关于两者的区别,先直接上图:

蓝色线代表网络读取(加载),红色线代表js执行,这俩都是针对脚本的;绿色线代表 HTML 解析

浏览器的解析渲染原理以及JS、CSS阻塞问题分析_第2张图片

这张图可以说是非常清晰了,结论:

  • 没有 defer 或 async,浏览器会立即加载并执行脚本,并且会阻塞DOM的解析
  • 有 async,浏览器立即加载脚本,加载的时候不会阻塞DOM解析,加载完了会立即执行,js执行时会阻塞DOM解析。如果两个async的标签,两者的执行顺序也无法预料,是谁先加载完就谁先执行,这一点很不可控
  • 有 defer,浏览器立即加载脚本,加载的时候不会阻塞DOM解析,但是 js执行要在DOM解析完成之后,DOMContentLoaded 事件触发之前。如果有两个defer的标签,两者的执行顺序是按照加载顺序来的,谁先加载谁先执行

CSS和JS引入位置说明

1:CSS资源尽量放在head部分
2:JS资源尽量放在body结束标签之前
3:CSS资源尽量优先于JS资源引入

引入位置解释

1:因为CSS解析和DOM解析可以同时进行,所以CSS资源放在头部不会影响DOM解析,而且放在头部也会优先开始加载CSS样式,在渲染DOM的时候也已经知道了自己的样式,所以一次就可渲染成功,如果将CSS放在底部,那么会优先渲染DOM,而浏览器为了更好的用户体验,渲染引擎会尝试尽快在屏幕上显示内容,也就是说渲染引擎会边解析、边渲染、边布局显示,已尽快减少白屏时间,所以随着CSS规则树的构建,还需要对之前渲染树重新渲染,可能会导致回流和页面跳动。

2:JS放在body标签结束之前,首先可以确保能取到需要操作的DOM对象,也可缩短因JS阻塞。而造成的白屏时间,提升用户体验。因为如果把JS放在head部分,JS运行会阻塞DOM树和CSS树构建,导致白屏时间延长,影响用户体验。

3:因为JS在运行时,如果需要操作CSS,但该CSS还没有下载和构建,则首先会阻塞JS线程,然后开启新线程去下载解析构建CSS规则树,再执行JS代码

浏览器引擎说明
之前浏览器内核分两个部分:渲染引擎和JS引擎,但由于JS引擎越来越独立,所以现在通常所谓的浏览器内核也就单指浏览器所采用的渲染引擎了

渲染引擎:负责对网页语法的解释(如标准通用标记语言下的一个应用HTML、JavaScript)并渲染(显示)网页
常见渲染引擎有Chrome、Safar采用Webkit,Firefox火狐采用Gecko,IE采用Trident。(目前Chrome开始采用Blink,IE采用Edge)

JS引擎:单线程引擎,负责执行JS代码
常见JS引擎有Chrome采用V8引擎,Safari采用Nitro,Firefox采用SpiderMonkey,微软采用JScript引擎

回流(reflow 重排)与重绘(repaint)
回流:又称重排,当渲染树的一部分或全部元素结构发生改变(增加、删除、位置改变,显示或隐藏),浏览器都需要重新计算一遍DOM结构,重新对当前的页面进行渲染,这就是回流。每个页面至少需要一次回流,就是在页面第一次加载的时候

重绘:当渲染树中的元素只是外观风格(背景色,字体颜色,边框颜色)改变,而不影响周围和内部布局时,这时浏览器只会重画某一部分,这就是重绘

注意

1:回流必定触发重绘,而重绘不一定触发回流
2:回流要比重绘更花费时间,也就更影响性能。所以在写代码时,要尽量避免过多的回流
3:display:none会触发回流,因为元素隐藏,布局发生了改变,而visibility:hidden只是隐藏元素,但还占着布局控件,所以只会发生重绘

如何减少回流和重绘

1:为需要多次回流的元素设置绝对定位或固定定位来脱离文档流,形成新的图层来限制回流和重绘的范围(如轮播图)

2:避免一条一条的修改样式,最好将样式定义为class并一次性更改

3:避免循环操作DOM。创建一个documentFragment或div,在它上面应用所有DOM操作,最后再把它添加到window.document

4:对一个元素进行复杂操作时,可以先隐藏,处理完成后再显示,这里所谓的隐藏就是让元素不存在于render tree中(先display:none 隐藏元素,然后对该元素进行所有的操作,最后再显示该元素。因对display:none的元素进行操作不会引起回流、重绘。所以只要操作只会有2次回流)

5:用transform做形变和位移

6:不要使用table布局,因为table的每一个小改动都会导致整个table重新布局

浏览器性能优化建议

1:减少HTTP请求数(如CSS精灵)

2:样式文件放在head中,脚本文件放在body标签结束前,这样可减少白屏时间,提升用户体验

3:避免一条一条的修改样式,最好将样式定义为class并一次性更改

4:资源合并与压缩(尽可能合并外部脚本,图片压缩)

5:为需要多次回流的元素设置绝对定位或固定定位来脱离文档流,形成新的图层来限制回流和重绘的范围(如轮播图)

6:对一个元素进行复杂操作时,可以先隐藏,处理完成后再显示,这里所谓的隐藏就是让元素不存在于render tree中

7:避免循环操作DOM。创建一个documentFragment或div,在它上面应用所有DOM操作,最后再把它添加到window.document

8:为图片指定大小,减少回流

9:少用全局变量,尽量使用局部变量

10:尽量用transform来做形变和位移,以减少回流

11:图片懒加载

12:对页面做节流和防抖

13:尽量减少在JS中进行DOM操作

14:简化并优化CSS选择器,尽量将嵌套层减少到最小

15:类型转换:把数字转换成字符串使用number + “” 效率最高

转载自

你可能感兴趣的:(javascript,css,javascript)