为什么要了解浏览器加载、解析、渲染这个过程?
- 了解浏览器如何进行加载,我们可以在引用外部样式文件,外部js时,将他们放到合适的位置,使浏览器以最快的速度将文件加载完毕。
- 了解浏览器如何进行解析,我们可以在构建DOM结构,组织css选择器时,选择最优的写法,提高浏览器的解析速率。
- 了解浏览器如何进行渲染,明白渲染的过程,我们在设置元素属性,编写js文件时,可以减少”重绘“”重新布局“的消耗。
渲染主流程
渲染引擎首先通过网络获得所请求文档的内容,下面是渲染引擎在取得内容之后的基本流程:
解析html以构建dom树->构建render树->布局render树->绘制render树
html的渲染过程就是:
- 将html代码按照深度优先遍历来生成DOM树。
- css文件下载完后也会进行渲染,生成相应的CSSOM。
- 当所有的css文件下载完且所有的CSSOM构建结束后,就会和DOM一起生成Render Tree。
- 接下来,浏览器就会进入Layout环节,将所有的节点位置计算出来。
- 最后,通过Painting环节将所有的节点内容呈现到屏幕上。
解释下几个概念:
DOM:Document Object Model,浏览器将HTML解析成树形的数据结构,简称DOM。
CSSOM:CSS Object Model,浏览器将CSS解析成树形的数据结构,简称CSSOM。
Render Tree:DOM和CSSOM合并成Render Tree。
layout:有了Render Tree,浏览器已经能知道网页中有哪些节点,各个节点的CSS定义以及他们的从属关系,从而去计算出Render Tree每个节点的具体位置。
painting:按照算出来的规则,通过显卡,把内容画到屏幕上。
回流(reflow)和重绘(repaint)
reflow(回流):当浏览器发现某个部分发生了点变化影响布局(如:尺寸、位置、隐藏/显示等),需要倒回去重新渲染,这个回退的过程叫回流。reflow会从这个root frame开始递归往下,依次计算所有的节点几何尺寸和位置。鼠标滑过、点击……只要这些行为引起了页面上某些元素的占位面积、定位方式、边距等属性的变化,都会引起它内部、周围甚至整个页面的重新渲 染。通常我们都无法预估浏览器到底会 reflow 哪一部分的代码,它们都彼此相互影响着。
通常有以下事件触发回流:
- 网页初始化时
- DOM操作(元素添加、删除、修改、元素顺序变化)
- 内容变化,包括表单域内文本改变
- CSS属性的计算或改变
- 添加或删除样式表
- 更改“类”属性
- 浏览器窗口的缩放、滚动等
- 伪类激活(例如:hover悬停)
注意:JS 获取 Layout 属性值(如:offsetLeft、scrollTop、getComputedStyle 等)也会引起回流。因为浏览器需要通过回流计算最新值
回流必将引起重绘,而重绘不一定会引起回流
repaint(重绘):是在一个元素的外观被改变,但是没有改变布局的情况下发生。如果只是改变某个元素的背景色、文字颜色、边框颜色等等不影响它周围或内部布局的属性时,将只会引起浏览器屏幕的一部分要重画。
注意:
- display:none 的节点不会被加入Render Tree,而visibility: hidden 则会,所以,如果某个节点最开始是不显示的,设为display:none是更优的。
- display:none 会触发 reflow(元素原来的位置不保留),而 visibility:hidden 只会触发 repaint(仅仅为视觉上的不可见,原来的位置仍然保留着),因为没有发现位置变化。
如何最小化重绘(repaint)和回流(reflow)
- 需要要对元素进行复杂的操作时,可以先隐藏(display:“none”),操作完成后再显示
- 需要创建多个 DOM 节点时,使用 DocumentFragment 创建完后一次性的加入 document
- 缓存 Layout 属性值,如:var left = elem.offsetLeft; 这样,多次使用 left 只产生一次回流
- 尽量避免用 table 布局(table 元素一旦触发回流就会导致 table 里所有的其它元素回流)
- 避免使用 css 表达式(expression),因为每次调用都会重新计算值(包括加载页面)
- 尽量使用 css 属性简写,如:用 border 代替 border-width, border-style, border-color 批量修改元素样式:elem.className 和 elem.style.cssText 代替 elem.style.xxx
DOM 树和渲染树的区别
- DOM 树与 HTML 标签一一对应,包括 head 和隐藏元素
- 渲染树不包括 head 和隐藏元素(… display=none),大段文本的每一个行都是独立节点,每一个节点都有对应的 css 属性;还有像一些节点的位置为绝对或浮动定位(需要css知识理解),这些节点会在文本流之外,因此会在两棵树上的不同位置,渲染树标识出真实的位置,并用一个占位结构标识出他们原来的位置。
浏览器解析css选择器
- 浏览器按“从右向左”读取。意味着在选择器 ul > li a[title=”home”] 中,首先被解析的是a[title=”home”]
- ID选择符最高效,通配选择符效率最低,解析速度由快到慢依次是:ID、class、tag和universal ;
- 不要用标签修饰id选择器 (永远不要这样做ul#main-navigation{}ID已经是唯一的,不需要Tag来标识,这样做会让选择器变慢。)
- 后代选择器是最糟糕的 html body ul li a { }
- • 6.CSS3的效率问题(CSS3选择器(比如:nth-child)能够漂亮的定位我们想要的元素,又能保证我们的CSS整洁易读。但是这些神奇的选择器会浪费很多的浏览器资源。);
- 我们知道#ID速度是最快的,那么我们都用ID,是不是很快。但是我们不应该为了效率而牺牲可读性和可维护性。
#test p{ color:#999999}
正解:遍历是自右向左,也就是先查询到p元素,再找到上一级id为test的元素。
#main-navigation { } /* ID (最快) */
body.home #page-wrap { } /* ID */
.main-navigation { } /* Class */
ul li a.current { } /* Class *
ul { } /* Tag */
ul li a { } /* Tag */
* { } /* 通配 (最慢) */
关于浏览器加载css和js
当我们的浏览器获得html文件后,会自上而下的加载,并在加载过程中进行解析和渲染。
加载说的就是获取资源文件的过程,如果在加载的过程中,遇到外部css文件和图片,浏览器会另外发出一个请求,来获取css文件和相应的图片,这个请求是异步的,并不会影响html文件。
但是如果遇到javascript文件,html文件会挂起渲染的线程,等待javascript加载完毕后,html文件再继续渲染。
为什么html需要等待javascript呢?
因为javascript可能会修改DOM,导致后续的html资源白白加载,所以html必须等待javascript文件加载完毕后,再继续渲染。这也就是为什么javascript文件要写在底部body标签前的原因。
css,js会影响dom吗?
首先必须明白浏览器从获取到代码到页面展示过程发生了什么,首先是解析DOM,生成DOM树,解析CSS文件,生成CSS树,然后DOM树和DOM树合并生成渲染树,再渲染生成页面。 结论1:由于DOM解析和CSS解析是分开的,所以CSS文件并不会阻塞DOM的解析。
页面渲染需要DOM树和DOM树合并生成渲染树,所以CSS文件会阻塞页面渲染。 结论2:CSS文件会阻塞页面渲染。
对于JS,首先要知道JS是单线程执行的,JS可以对DOM的结构进行操作,比如删除、修改、新增dom。所以遇到JS文件,页面会停止DOM解析,如果不停止DOM解析,即一边执行JS,一边解析DOM,解析完DOM之后,发现JS修改了DOM结构,那么之前解析的DOM不就白费了吗?浏览器才不会这么傻呢?所以 结论3:遇到JS文件,页面会停止DOM解析
JS文件还会去读取CSS,所以执行JS还必须等到CSS文件解析完之后,这就相等与CSS文件阻塞了JS 结论4:CSS文件会阻塞JS