一、DOM/CSS/渲染树
(1)DOM树构建(深度优先解析原则)
浏览器引擎把html结构变成一个又一个dom节点,然后排列形成一个树形结构,这个树形结构就是dom树
dom树的构建就是对元素节点的解析
(2)CSS树构建(样式结构体)
浏览器引擎通过查看css样式对应元素与其它元素之间的层次关系,然后通过这个层次关系来构建css树
与dom树一样也是满足深度优先的原则
特点
会忽略浏览器不兼容、不能识别、无效的样式(比如用删除线划掉的样式)
(3)渲染树构建(renderTree)
渲染树是dom树结合css树形成的一个新的树。当renderTree构建完毕后,浏览器会根据它去绘制页面。
特点
- 渲染树每个节点都有自己的样式。
- 不包含隐藏节点(display: none)、不需要绘制的节点(html/head/style/title),这些节点在renderTree形成之前就会被剔除。
- visibility: hidden相对应的节点是包含在渲染树上的,因为它影响布局(layout)。
- 渲染树上的每一个节点都会被当成一个盒子,具有内容填充、边距、边框、位置、大小及其它样式。
二、解析与加载
(1)解析与加载
概念
页面渲染机制主要分为解析和加载
解析:解析就是把整个html结构变成一个又一个的节点,然后挂在dom树上面形成dom树
加载:加载就是加载html里面的资源
先有解析,再有加载,而解析和加载的过程是一个异步的过程
示例1
<img src="baidu.png" alt="" />
先是解析img标签变成节点再挂到dom树上,图片的加载并不在解析的过程当中,加载在当前节点解析完成之后
示例2
在构建dom树的时候,span和div会挂在dom树上面吗?
<span style="display: none"></span>
var oDiv = document.createElement('div');
document.body.appendChild(oDiv);
会,因为节点树不会管样式,只管dom节点,动态创建也是dom节点
三、回流与重绘
(1)回流与重绘
概念
当JS对页面的节点进行操作时,就会产生回流或重绘
回流/重排(reflow): 因为节点的尺寸、布局、显示(display: none/block)改变这些属性改变的时候,渲染树中的一部分或者全部需要重新构建,这种重新构建的现象就是回流。一个页面至少有一次回流。
重绘(repaint):
回流时,浏览器会看重新构建受影响部分的渲染树,只要渲染树一被改变或重新构建,就一定会引起重绘;
回流完成后,浏览器根据新的渲染树重新绘制回流影响的部分节点,这个重新绘制的过程叫做重绘;
不改变节点原有的尺寸、布局、显示、增删,就只会引起重绘。
小结
回流一定会引起重绘,重绘不一定是回流产生的后续反应。因为只要不是改变物理的位置、尺寸、显示,就不会引起回流。
引起回流的因素:
- DOM节点增加、删除
- DOM节点位置变化
- 元素的尺寸、边距、填充(文字、图片)、边框、宽高
- DOM节点display显示与否,不包含visibility
- 页面渲染初始化(第一次加载页面)
- 浏览器窗口尺寸变化(resize)
- 向浏览器请求某些样式信息(offset、scroll、client、width/height、getComputedStyle、currentStyle)
除开以上几个因素以外只会引起重绘。
dom操作之所以消耗性能,就是因为容易引起回流,所以在做dom操作优化的时候就是以减少回流次数为依据来进行优化的,之所以需要缓存、文档碎片就是为了减少回流次数。
示例1
<div class="box">我是一个孤独的盒子</div>
<script>
// 专给一个节点增加样式就可以这样缓存
var oBoxStyle = document.getElementsByClassName('box')[0].style; //点语法是消耗性能的,这里缓存可以优化性能
// 回流 + 重绘
oBoxStyle.width = '200px';
// 回流 + 重绘
oBoxStyle.height = '200px';
// 回流 + 重绘
oBoxStyle.margin = '20px';
// 重绘
oBoxStyle.backgroundColor = 'green';
// 回流 + 重绘
oBoxStyle.border = '5px solid orange';
// 重绘
oBoxStyle.color = '#fff';
// 回流 + 重绘
oBoxStyle.fontSize = '30px';
</script>
之所以不建议一行一行写,就是因为这样写容易引起回流
示例2
<div class="box">我是一个孤独的盒子</div>
<script>
var h1 = document.createElement('h1');
h1.innerHTML = '我是一个孤独的标题';
document.body.appendChild(h1)
</script>
h1追加在box后面只引起一次回流与重绘,但是如果追加在box前面的话,整个body里面的元素都会重新构建。所以要尽量避免插入在最前面。
在写dom的时候要考虑两个问题:
- 回流的问题;
- 回流所涉及到的节点数(一个父节点重新构建,里面的所有子节点都会重新构建)
(2)队列策略
概念
它是浏览器优化性能的一种机制。
原理
浏览器引擎里面有一个存放操作的队列,只要一加载页面就会看有多少会引起回流与重绘的操作,然后按照顺序放入队列,放到了一定的数量或时间间隔以后批量的处理,处理完毕以后清空队列。
TIP:批处理只会引起一次回流与重绘。
当向浏览器请求某些样式信息(offset、scroll、client、width/height)的时候,就会清空队列单独计算,防止影响样式的取值。
(3)减少回流次数和影响规模
1、静态添加样式属性-css类名
<head>
<style>
div {
width: 100px;
height: 100px;
background-color: #000;
}
div.active {
width: 200px;
height: 200px;
background-color: green;
border: 5px solid orange;
}
</style>
</head>
<body>
<div></div>
<script>
var oDiv = document.getElementsByTagName('div')[0];
oDiv.onmouseover = function () {
oDiv.className = 'active';
// this.style.width = '200px';
// this.style.height = '200px';
// this.style.backgroundColor = 'green';
// this.style.border = '5px solid orange';
}
</script>
</body>
加类名属于批量处理,只会引起一次回流与重绘
2、动态添加样式属性-cssText
<head>
<style>
div {
width: 100px;
height: 100px;
background-color: #000;
}
div.active {
width: 200px;
height: 200px;
background-color: green;
border: 5px solid orange;
}
</style>
</head>
<body>
<div></div>
<script>
var oDiv = document.getElementsByTagName('div')[0],
width = 200,
height = 200,
backgroundColor = 'green',
border = '5px solid orange';
oDiv.onmouseover = function () {
oDiv.style.cssText = '\
width:'+ width + 'px;\
height:'+ height + 'px;\
background-color:' + backgroundColor + ';\
border: '+ border + ';\
';
}
</script>
</body>
3、文档碎片
创建10个盒子
<head>
<style>
.box {
width: 100px;
height: 100px;
margin-bottom: 10px;
background-color: #000;
}
</style>
</head>
<body>
<script>
for (var i = 0; i < 10; i++) {
var oDiv = document.createElement('div');
oDiv.className = 'box';
document.body.appendChild(oDiv);
}
</script>
</body>
这样写回流了10次
<head>
<style>
.box {
width: 100px;
height: 100px;
margin-bottom: 10px;
background-color: #000;
}
</style>
</head>
<body>
<script>
var oFragment = document.createDocumentFragment();
for (var i = 0; i < 10; i++) {
var oDiv = document.createElement('div');
oDiv.className = 'box';
oFragment.appendChild(oDiv);
}
document.body.appendChild(oFragment);
</script>
</body>
用文档碎片就只回流了1次
4、display技巧
<head>
<style>
.box {
width: 100px;
height: 100px;
margin-bottom: 10px;
background-color: #000;
}
</style>
</head>
<body>
<div class="box"></div>
<script>
var oBox = document.getElementsByClassName('box')[0],
oBoxStyle = oBox.style;
oBox.onmouseover = function () {
oBoxStyle.display = 'none';
oBoxStyle.width = '200px';
oBoxStyle.height = '200px';
oBoxStyle.backgroundColor = 'green';
oBoxStyle.border = '5px solid orange';
oBoxStyle.display = 'block';
}
</script>
</body>
因为渲染树不包括display:none的节点,所以可以给节点隐藏,改变完了以后再显示,这个就回流2次,重绘2次
(4)注意事项
1、获取样式信息时一定要缓存
div.style.left = div.offsetLeft + 10 + 'px';
在使用offset、client、scroll、width、height、getComputedStyle、currentStyle的时候,一定不能像上面那样写,否则每做一次就会产生回流与重绘,很消耗性能。
正确写法:
var oLeft = div.offsetLeft;
div.style.left = oLeft + 10 + 'px';
这样写每次请求样式信息的时候,就直接用的变量,就不会产生回流。
TIP:如果是动态的改变,比如每10秒修改一次,就只能去获取,获取的方法最好用getStyles,因为它的封装做了优化处理,性能比offset好很多。
2、动画元素一定要绝对定位
因为绝对定位脱离了文档流,新建一个定位层,所有元素产生了回流都不会影响到父级。
如果用margin来操作动画元素,每做一次整个父级都会引起回流与重绘(塌陷)。
3、尽量不要使用table布局
table回流的代价很大,因为table具有弹性性质,而且table本身具备内边距,这个内边距和普通的内边距不一样,它是cellpadding,它对回流的影响相当的大。
做表格如果没有特定的需求尽量不要用table来做,可以用ul、li来做。