高性能JavaScript
author: @TiffanysBear
从《高性能JavaScript》一书中的整理笔记:
1、将经常使用的对象成员、数组项、和域外变量存入局部变量
原因:数据存储位置对大地代码整体性能会产生重要的影响,直接变量和局部变量的访问速度快于数组和对象成员。因为局部变量位于作用域链的第一个对象中,全局变量位于作用域链的最后一环。变量在作用域链的位置越深,访问的时间就越长。
1 | var doc = document; |
2、避免使用with表达式,因为他改变了运行期上下文的作用域链。
3、同理with,也要注意使用try-catch,因为catch也会改变运行期上下文的作用域链。
4、嵌套成员变量会造成重大的性能影响,尽量少用。
5、DOM操作量化问题:
1 |
|
6、克隆已有的DOM元素,即element.cloneNode(),比起新建节点来说,即element.createElement(),会快一点,但是性能提高不是很大。
7、遍历数组明显快于同样大小和内容的HTML集合
8、 for循环时,HTML某元素集合的长度不建议直接作为循环终止条件,最好将集合的长度赋给一个变量,然后使用变量作为循环终止条件;
原因:当每次迭代过程访问集合的length时,它导致集合器更新,在所有的浏览器上都会产生明显的性能损失。
9、需要考虑实际情况的优化,根据7,可以将集合中的元素通过for循坏赋值到数组中,访问数组的数组快于集合。但是要注意对于复制的开销是否值得。
1 | function toArray(collection) { |
10、获取DOM节点,使用nextSibling方式与childNodes方式,在不同的浏览器中,这两种方法的时间基本相等。但是在IE中,nextSibling比childNodes好,IE6下,nextSibling比对手快16倍,在IE7下,快105倍。因此,在老的IE中性能严苛的使用条件下,用nextSibling较好。
11、querySelectorAll()可以联合查询,即querySelectorAll(‘div .warning,div .notice’),在各大浏览器中支持也挺好的,还可以过滤很多非元素节点;
这个网站是:canIuse,可以检查HTML、CSS元素在各大浏览器的兼容情况,一个很有用的网站!
12、重绘和重排版;
重绘:不需要改变元素的长度和宽度,不影响DOM的几何属性;
重排版:影响了几何属性,需要重新计算元素的几何属性,而且其他元素的几何属性有可能也会受影响。浏览器会在重排版过程中,重新绘制屏幕上受影响的部分。
获取布局信息的操作将导致刷新队列的动作,如使用:offsetTop
、offsetLeft
、offsetWidth
、offsetHeight
、scrollTop
、scrollLeft
、scrollWidth
、clientTop
、clientLeft
、clientHeight
、geteComputedStyle()
(在IE中此函数成为currentStyle);浏览器此时不得不进行渲染队列中带改变的项目,并重新排版以返回正确值。
解决办法:
通过延迟访问布局信息避免重排版。
整体修改cssText的css代码,而不是分开访问,修改cssText的属性
1 | // 访问了4次DOM,第二次开始重排列并强迫渲染队列执行 |
改变css类名来实现样式改变
当对DOM元素进行多次修改时,可以通过以下的步骤减少重绘和重排版的次数:
(注意:此过程引发两次重排版,第一次引发一次,第三次引发一次。如果没有此步骤的话,每次对第二步的改变都有可能带来重排版。)
从文档流中摘除该元素,摘除该元素的方法有:
a、对其应用多重改变
b、将元素带回文档中
c、使其隐藏,进行修改后在显示
d、使用文档片段创建子树,在将他拷贝进文档
1 | var doc = document; |
创建一个节点的副本,在副本上进行修改,再让复制节点覆盖原先节点
1 | // 创建一个节点的副本,在副本上进行修改,再让复制节点覆盖原先节点 |
ps:推荐第二种,因为其涉及最少数量的操作和重排列。
14、减少对布局信息的查询次数,查询时将他赋值给局部变量参与计算。
例子,在元素网右下方不断平移时,在timeout中可以写:
1 | var current = myElement.offsetLeft; |
15、大量的元素使用:hover之后,页面性能将降低,特别是IE8中。因此强烈建议,在数据量很大的表格中,减少鼠标在表上移动效果,减少高亮行的显示,使用高亮是个慢速过程CPU使用率会提高到80%-90%,尽量避免使用这种效果。
16、事件托管
讲到事件托管,首先我们来看一看冒泡机制:
DOM2级事件规定事件包括三个阶段: ① 事件捕获阶段 ② 处于目标阶段 ③ 事件冒泡阶段
图片引用来源:http://www.w3.org/TR/DOM-Level-3-Events/#event-flow
如下图的实验结果可以知道,当我们点击了inner之后,捕获和冒泡结果如上图的规律相同;
因此,因为每一个元素有一个或多个事件句柄与之相连时,可能会影响性能,毕竟连接每一个句柄都是有代价的,所以我们采用事件托管技术,在一个包装元素上挂接一个句柄,用于处理子元素发生的所有事件。
下面我们以如下的dom结构为例:
假如有一个ul,下面有很多个li:
1 | <div> |
当某个Li被点击的时候需要触发相应的处理事件。我们通常的写法,是为每个Li都添加onClick的事件监听。
1 | function addListenersLi(liNode) { |
如果li足够多,或者对于li的操作特别频繁,为每一个li绑定一个点击事件将会特别影响性能,因为在此期间,你需要访问和修改更多的DOM节点,事件的绑定过程发生在onload事件中,绑定本身也非常耗时;同时,浏览器需要保存每个句柄的记录,很占用内存。重点是有些绑定了还不一定会用着,并不是100%的按钮或链接都会被点到的哟!
因此,采用事件托管更为高效,当事件被抛到更上层的父节点的时候,我们通过检查事件的目标对象(target)来判断并获取事件源Li。下面的代码可以完成我们想要的效果:
1 | var oul = document.getElementById('ulList'); |