数据存储
在JS中,数据存储的位置会对代码整体性能产生重大的影响(因为作用域的读取顺序)。
数据存储共有4种方式:字面量、变量、数组、对象成员。
通过以下策略,你可以显著提升JS的实际性能:
- 访问字面量和局部变量的速度最快,相反,访问数组元素和对象成员相对较慢。
- 由于局部变量存在于作用域链的起始位置,因此访问局部变量比访问跨作用域变量更快。变量在作用域链的位置越深,访问所需时间就越长。由于全局变量总处于作用域链的最末端,因此访问速度也是最慢。
- 嵌套的对象成员会明显影响性能,尽量少用。
- 属性或方法在原型链中的位置越深,访问它的速度也越慢。
- 通常来说,你可以通过把常用的对象成员、数组元素、跨域变量保存在局部变量中来改善JS性能,因为局部变量访问速度更快。
- 避免使用with语句,因为它会改变执行环境作用域链。同样,try-catch语句中的catch子句也有同样的影响,因此也要小心使用。
对DOM的操作
文档对象模型(DOM)是一个独立于语言的,用于操作XML和HTML文档的程序接口(API)。在浏览器中,主要用来与HTML文档打交道,我们可以使用DOM API来访问文档中的数据。浏览器中通常会把DOM和JS独立实现。这对性能意味着什么?简单理解,两个相互独立的功能只要通过接口彼此连接,就会产生消耗。有个贴切的比喻,把DOM和JS(ECMAScript)各自想象为一个岛屿,它们之间用收费桥梁连接。JS每次访问DOM,都要途径这座桥,并交纳“过桥费”。访问DOM的次数越多,费用也就越高。即访问DOM的次数越多,代码的运行速度越慢。因此,通用的经验法则是:减少访问DOM的次数,把运算尽量留在JS这一端处理。一般来说,对于任何类型的DOM访问,需要多次访问同一个DOM属性或方法时,最好使用一个局部变量缓存此成员。当遍历一个集合时,第一优化原则是把集合存储在局部变量中,并把length缓存在循环外部,然后,使用局部变量替代这些需要多次读取的元素。
// 减少对DOM的操作案例
funtcion collectionNodesLoal(){
var coll = document.getElementsByTagName('div'),
len = coll.length,
name = '',
el = null;
for (var count = 0; count < len; count++){
el = coll[count];
name = el.tagName;
}
return name;
}
重排和重绘
为了更好的知道重排和重绘是如何影响性能的,我们先了解一下DOM树和渲染树的区别
DOM树:表示页面结构
渲染树:表示DOM节点如何显示
需要注意:DOM树中的每一个需要显示的节点在渲染树中至少存在一个对应的节点(隐藏的DOM元素在渲染树中没有对应的节点)。渲染树中的节点被称为“盒”,也就是CSS中的盒模型,一个盒子具有padding、margin、border、content、position。一旦DOM和渲染树构建完成,浏览器就开始显示(绘制)页面元素
当DOM的变化影响了元素的几何属性(宽和高),比如改变边框宽度或给段落增加文字导致行数增加等等,浏览器需要重新计算元素的几何属性,同样的,其他元素的几何属性和位置也会因此受到影响。浏览器会使渲染树中受到影响的部分失效,并重新构造渲染树,这个过程称为重排。完成重排后,浏览器会重新绘制受影响的部分到屏幕中,该过程称为重绘。并不是所有的DOM改变都会影响几何属性。例如,改变一个元素的背景颜色并不会影响它的宽和高。在这种情况下,只会发生一次重绘(不需要重排),因为元素的布局并没有改变。重排和重绘操作都是代价 昂贵的操作,它们会导致web应用程序的UI反应迟钝。所以应当尽可能减少这类过程的发生
下述情况会发生重排:
- 添加或删除可见的DOM元素
- 元素位置改变
- 元素尺寸改变(包括:外边距、内边距、边框厚度、宽度、高度等属性改变)
- 内容改变(例如:文本改变、图片被另一个不同尺寸的图片替代)
- 页面渲染器初始化
- 浏览器窗口尺寸改变
需要注意:浏览器会通过队列化修改和批量执行的方式最小化重排次数。但是,当你查询布局信息时,比如获取偏移量、滚动位置等等时,浏览器为了返回最新值,会刷新队列并应用所有变更。最好的做法是尽量减少布局信息的获取次数,获取后把它赋值给局部变量,然后再操作局部变量。
重绘和重排可能代价非常昂贵,因此一个好的提高程序响应速度的策略就是减少此类操作的发生。为了减少发生次数,应该合并多次对DOM样式的修改,然后一次处理掉。
// 合并所有的改变然后一次处理,这样只会修改DOM一次,会高效很多
// 批量修改并覆盖原有的样式
var el = document.getElementById('myDiv');
el.style.cssText = 'padding: 5px; margin: 5px;';
// 批量修改并保留原有的样式
el.style.cssText += '; border: 1px solid #ccc;';