浏览器渲染页面过程描述,DOM编程技巧以及重排和重绘。

1、浏览器渲染页过程描述html

一、浏览器解析html源码,而后建立一个DOM树。ajax

在DOM树中,每个HTML标签都有一个对应的节点(元素节点),而且每个文本也都有一个对应的节点(文本节点)。编程

DOM树的根节点就是documentElement,对应的是html标签。数组

二、浏览器解析CSS代码,计算出最终的样式数据。浏览器

对CSS代码中非法的语法它会直接忽略掉。缓存

解析CSS的时候会按照以下顺序来定义优先级:浏览器默认设置,用户设置,外联样式,内联样式,html中的style(嵌在标签中的样式)。数据结构

三、建立完DOM树并获得最终的样式数据以后,构建一个渲染树。app

渲染树和DOM树有点像,可是有区别。DOM树彻底和html标签一一对应,而渲染树会忽略不须要渲染的元素(head、display:none的元素)。布局

渲染树中每个节点都存储着对应的CSS属性。性能

四、当渲染树建立完成以后,浏览器就能够根据渲染树直接把页面绘制到屏幕上。

2、高性能Javascript DOM编程

咱们知道,用脚本进行DOM操做的代价很昂贵。把DOM和ECMAScript各自想象为一个岛屿,它们之间用收费桥梁连接,ECMAScript每次访问DOM,都要通过这座桥,并交纳“过桥费”,访问DOM的次数越多,费用也就越高。所以,推荐的作法是尽可能减小过桥的次数,努力呆在ECMAScript岛上。这是比喻很是形象。那么怎样才能高效呢?

一、DOM访问与修改

访问DOM元素是有代价的(这里其实和ajax调后台数据接口是同样,DOM是用于操做XML和HTML文档的应用程序接口,一次能完成的事不要请求屡次),经过DOM获取元素以后,修改元素的代价更是昂贵,由于它会致使浏览器从新计算页面的几何变化(重排和重绘)。

例子:

var times = 15000;

//code1

console.time('time1');

for(var i=0; i<times; i++){

  document.getElementById('myDiv1').innerHTML +='a';

}

console.timeEnd('time1');

//code2

console.time('time2');

var str = '';

for(var i=0; i<times; i++){

  str += 'a';

}

document.getElementById('maDiv2').innerHTML = str;

console.timeEnd('time2');

结果time1:2352.064ms/time2:0.789ms

第一段代码的问题在于,每次循环迭代,改元素都会被访问两次:一次读取innerHTML的值,另外一次重写它。

结果充分证实,访问DOM的次数越多,代码的运行速度越慢。

所以,能减小DOM访问的次数就尽可能减小,尽可能留在ECMAScript这端处理。

二、html集合&遍历DOM

操做DOM另外一个耗能点就是遍历DOM,在平时获取一组元素的时候(getElementsByTagName方法),收集的结果是一个类数组对象,它处于一种“实时状态”实时存在,这意味着当底层文档对象更新时,它也会自动更新。

例子:

而这正是低效之源!很简单,跟数组的优化操做同样,缓存一个length变量就OK了(读取一个集合的length比读取一个普通数组的length要慢不少,由于每次都要查询):

console.time('time0');

var lis0 = document.getElementsByTagName('li');

var str0 = '';

for(var i=0; i<lis0.length; i++){

  str0 +=lis0[i].innerHTML;

}

console.timeEnd('time0');

console.time('time1');

var lis1 = document.getElementsByTagName('li');

var str1 = '';

for(var i=0; len=lis1.length; i<len; i++){

  str1 += lis1[i].innerHTML;

}

console.timeEnd('time1');

结果:time0:0.237ms/time1:0.099ms

当获取的这个类数组对象长度值特别大的时候(如1000),性能提高仍是很明显的。

第一部分主要提了两点优化,一是尽可能减小DOM的访问,把运算放在ECMAScript这一端,二是尽可能缓存局部变量,好比类数组对象的length。

3、重排和重绘

一、什么是重排和重绘

浏览器下载完页面中的全部组件(html标记、Javascript、CSS、图片)以后会解析生成两个内部数据结构——DOM树和渲染树。

DOM树表示页面结构,渲染树表示DOM节点如何显示。DOM树中的每个须要显示的节点在渲染树中至少存在一个对应的节点(隐藏的DOM元素 display:none 在渲染树中没有对应的节点)。

渲染树中的节点被称为‘帧’或‘盒’,符合CSS模型的定义,页面元素为一个具备填充,边距,边框和位置的盒子。一旦DOM树和渲染树构建完成,浏览器就开始绘制页面元素。

当DOM的变化影响了元素的几何属性(宽或高),浏览器须要从新计算元素的几何属性,同时其余元素的几何属性和位置也会受到影响。浏览器会使渲染树中受到影响的部分失效,并从新构造渲染树,这个过程称为重排。完成重排后,浏览器会从新绘制受到影响的部分到屏幕,这个过程称为重绘。

因为浏览器的流布局,对渲染树的计算一般只须要遍历一次就能够完成。table及其内部元素除外,它可能须要屡次计算才能肯定好其在渲染树中节点的属性,一般要花三倍同等元素的时间。这也是为何咱们要避免使用table作布局的一个缘由。

并非全部的DOM变化都会影响几何属性,好比改变一个元素的背景色并不会影响元素的宽和高,这种状况下只会发生重绘。

二、重排什么时候发生

a、添加或删除可见的DOM元素

b、元素位置改变

c、元素尺寸改变

d、元素内容改变

e、页面渲染初始化

f、浏览器窗口尺寸改变

三、渲染树变化的排队和刷新

获取布局信息的操做会致使队列刷新,好比:

a、offsetTop,offsetLeft,offsetWidth,offsetHeight

b、scrollTop,scrollLeft,scrollWidth,scrollHeight

c、clientTop,clientLeft,clientWidth,clientHeight

d、getComputeStyle()||currentStyle(IE)

由于offsetHeight属性须要返回最新的布局信息,所以浏览器不得不执行渲染队列中的“待处理变化”并触发重排以返回正确的值(即便队列中 改变的样式属性和想要获取的属性值并无什么关系)。

四、最小化重排和重绘

改变元素多种样式的时候,最好用className,一次性完成操做,这样只会修改一次DOM。

五、文档碎片

文档碎片的设计初衷就是为完成这类任务——更新和移动节点。当你附加一个片断到节点时,实际上被添加的是该片断的子节点,而不是片断自己。只会触发一次重排,并且只访问一次实时的DOM。

用法:

var oFragment = document.createDocumentFragment();

for(var i=0; i<1000; i++){

  var oP = document.createElement('p');

  oP.innerHTML = i;

  oFragment.appendChild(oP);

}

var oDo = document.body;

oDo.appendChild(oFragment);

六、让动画脱离文档流

使用绝对位置定位页面上的动画元素,将其脱离文档流。这样不会致使文档流中的元素受到影响,不会大规模的进行重排和重绘。

第二部分重排和重绘是DOM编程中耗能的主要缘由之一,为了不没必要要的性能损耗能够参考一下几点:

一、尽可能不要在布局信息改变时作查询(会致使渲染队列强制刷新);

二、同一个DOM的多个属性改变能够写在一块儿(减小DOM访问,同时把强制渲染队列刷新的风险降为0);

三、若是要批量添加DOM,能够先让元素脱离文档流,操做完成后带人文档流,这样只会触发一次重排(fragment元素的应用);

四、将须要屡次重排的元素,添加定位属性,设置为absolute,fixed,这样此元素就脱离了文档流,不会影响其余元素。

相关文章
相关标签/搜索