文档对象模型 (DOM)是一个独立于特定语言的应用程序接口。
1:在浏览器中,DOM是以javascript实现的,通过javascript来操作浏览器页面的元素。
2:DOM提供了丰富的接口,但是DOM操作代价很高。
3:页面前端代码的性能瓶颈大多集中在DOM操作上。
4:前端性能优化主要关注点是DOM操作优化。
5:DOM操作优化总原则是尽量减少DOM操作。
DOM操作为什么会影响性能。
1:DOM的实现和ECMAscript的实现是分离的。
--IE中,ECMAscript的实现在jscript.dll中,而DOM的实现在mshtml.dll中
--Chrome中,ECMAscript的实现在V8引擎中,而DOM的实现在webkit中的webCore。
其他浏览器类似,所以通过javascript 代码调用DOM借口,相当于两个独立模块的交互,相比较在同一模块的调用,这种跨模块调用性能损耗很高。(但是DOM操作对性能影响最大的还是浏览器的repaint 和reflow 重绘和重排)。
简述下浏览器渲染原理:从下载文档到渲染页面的过程中
1:浏览器会通过解析HTML文档来构建DOM树
2:解析CSS产生CSS规则树
3:解析js代码,这可能会修改生成的DOM树和CSS规则树
4:根据DOM树和CSS树构建渲染树(css会根据选择器匹配HTML元素)
5:渲染树包含每个元素的大小,边距等样式属性,不包含隐藏元素,head等不可见元素。
6:浏览器根据元素的坐标和大小来计算每个元素的位置,并绘制这些元素到页面上。
7:重绘是指页面的某些部分要重新绘制,比如颜色或者背景色,但是元素的位置和尺寸并没改变。
8:重排是指元素的位置或者尺寸发生改变,浏览器需要重新计算渲染树,导致渲染树一部分或者全部发生变化。渲染树重新建立后,浏览器会重新绘制页面上受影响的元素。
9:重排比重绘的代价高很多。
现代浏览器会针对重绘和重排做性能优化,比如,把DOM操作积累一批后统一做一次重排或重绘。但有些情况浏览器会立即重排或重绘,比如:请求DOM元素布局信息:
offsetTop/Left/Width/Height、scrollTop/Left/Width/Height、clientTop/Left/Width/Height、getComputedStyle()、currentStyle.这些值都是动态计算的,所以浏览器需要尽快完成页面的绘制,然后计算返回值,从而打乱了重排或重绘的优化。
重排和重绘是不可避免的,但是可以降低他们带来的影响:
1: 合并多次DOM操作。
1 for(...){ 2 document.getElementById('ele').innerHTML="a_string"; 3 } 4 5 for(...){ 6 arr[].push 7 }
2:把DOM元素离线或者隐藏后修改。(适合需要那些大批量修改DOM元素的情况)
(2.1):使用文档片段。
--文档片段是一个轻量级的document对象。
--不会和特定页面关联。
--通过在文档片段上进行DOM操作可降低DOM操作对页面性能影响。
--操作完成后附加在页面中。
--对页面性能影响只存在于最后把文档片段附加到页面这一步操作上。
1 var fragment = document.createDocumentFragment(); 2 //基于fragment的大量DOM操作 3 document.getElementById('myEle').appendChild(fragment);
(2.2):设置DOM元素的display样式为none来隐藏元素。
--通过隐藏DOM元素,达到在页面中移除元素的效果。
--经过大量的DOM操作后恢复元素原来的display样式。
1 var myEle = document.getElementById("myEle"); 2 myEle.style.display="none"; 3 //基于myEle的大量的DOM操作 4 myEle.style.display="block";
(2.3):克隆DOM元素到内存中。
--把页面的DOM元素克隆一份到内存中。
--再在内存中操作克隆的元素。
--操作完成后用此克隆的元素替代页面中原来的DOM元素。
1 var old = document.getElementById('myEle'); 2 var clone = old.cloneNode(true); 3 //一些基于clone的大量DOM操作 4 old.parentNode.prelaceChild(clone,old);
现代浏览器中因为有DOM操作优化,所以应用如上方式可能不能明显感受到性能改善,但是仍有市场的旧浏览器中,应用以上者三种编码方式可大幅提高页面渲染性能。
3:设置具有动画效果的DOM元素的position属性为fixed或absolute。
--设置动画效果的元素为绝对定位
--使元素脱离页面布局流
--避免频繁的重排,只涉及动画元素自身的重排。
--可提高动画效果的展示性能。
如果把动画设为绝对定位不符合要求,那可以在动画开始时设为绝对定位,等动画结束后恢复原始的定位设置。
4:谨慎取得DOM元素的布局信息。
--获取DOM元素的布局信息会强制浏览器刷新渲染树,并可能会导致页面的重排或重绘。
--如果需要这些布局信息,最好是在DOM操作之前就取得。
1 var newWidth = div1.offsetWidth+10; 2 div1.style.width = newWidth +'px'; 3 4 var newHeight = div2.offsetHeight + 10; 5 div2.style.Height = newHeight +'px';
代码在遇到取得DOM元素信息时会触发页面重新计算渲染树,所以上边代码会导致页面重排两次,如果把取得的DOM元素布局信息提前,因为浏览器会优化连续的DOM操作,所以实际上只会有一次的页面重排出现。
1 var newWidth=div1.offsetWidth+10; 2 var newHeight = div2.offsetHeight+10; 3 4 div1.style.width = newWidth+"px"; 5 div2.style.height = newHeight + "px";
5:事件托管的方式来绑定事件。
--DOM元素绑定事件会影响页面性能。一方面,绑定事件本身会占用处理时间,另一方面,浏览器保存事件绑定会占用内存。
--页面元素绑定的事件越多,占用的处理时间和内存就越大。性能越差。
--页面绑定的事件越少越好。
--优雅的解决方法是事件托管方法(利用事件冒泡机制,只在父元素上绑定事件处理,用于所有子元素的事件,在事件处理函数中根据传入的参数判断事件源元素,针对不同的源元素做不同的处理,这样就不需要给每个子元素都绑定事件了。)
--可以很方便的添加或删除子元素,不需要考虑因子元素移除或改动而需要修改事件绑定。
1 document.getElementById('list').addEventListener("click", function(e){ 2 //检查事件源元素 3 if(e.target && e.target.nodeName.toUpperCase() == "LI"){ 4 //针对子元素的处理
console.log("clicked"+e.target.innerHTML);
5 } 6 })
上边的代码里,只在父元素绑定了click事件。当点击子节点时,click事件会冒泡,父节点捕捉事件后通过e.target检查事件源元素并做相应处理。但是事件绑定方式存在浏览器兼容问题。所以在很多框架提供了接口方法用于事件托管。比如 Jquery
1 $("table").on("click","td",function(){ 2 $(this).toggleClass("chosen"); 3 });