折腾了好几天,纠结了好几天,郁闷了好几天,终于在今天可以释怀了,留下其中的苦乐辛酸来和大家分享。
事情是这样子的,上周接到一个需求,其中涉及到一个好友选择的组件,就是单机左侧某个群组下的好友后该好友移动到右侧,视为选择了它,另外每个群组还有全选,折叠和展开等功能。组件在开发过程中,遇到了在2000条数据的时候,搜索会变的很卡,IE8下为2s左右,IE6更长,我在最初的优化过程中我使用事件代理,文档片段,字符串数组,少创建对象,缓存一切可以缓存的,用hash超找代替数组查找等方式,但是一直没有找到性能的瓶颈所在,所以效果不大,后来采用了setTimeout的方式,就是限制js的执行时间,当它超过规定的时间后记录当前的执行状态,然后setTimeout延迟一段时间再执行,而把延时的这段时间交给UI线程,这样做后确实不卡了,但是因为出现延时调用,搜索的总体时间变长了,试想让用户等十多秒来看搜索的结果这样做也太不合理,问题一直没有得到根本的解决。
后来,在johnnie和youkun的帮助和提示下,我找到了优化的入口就是找性能瓶颈,就是如果一个段代码很耗时的话你可以注释掉一部分你认为是很耗时的,或者干脆全部注释掉,然后再一点一点的解开,按照这个思路,我发现在我的循环搜索中频繁进行了dom操作,就比如搜索用户提交的一个queryString,我的方法是在数据中遍历所有的item,如果匹配,就通过这条数据找到这个item对应的dom,然后显示它,不匹配就隐藏它,然后每个组搜索完还要更新该组搜索的结果数,这里面涉及到选择dom,然后显示或者隐藏它等,正是这些dom操作在大数据量的时候使得搜索操作变得很慢,当时我试验了下,就算在循环中只执行一个document.getElementById也很耗时,找到了问题的根源所在,接下来的优化就有了方向,于是在v3版本中我改变了搜索的策略,循环遍历的时候只拼接dom,在循环结束后一次性的放到文档中,经过测速,循环遍历拼接dom只用了不超过60ms,而把这一大段html放到文档中这一个操作在IE下耗时250ms左右,虽然离大师们推荐的100ms还有差距,但是相对v2的2s提升了很多,我也终于松了一口气了。
小结:其实js优化一定要掌握方法,没有正确的方法可能做了很多工作都是偏离主题的,按照正确的优化方法来优化就会定位性能瓶颈,然后对其采取进行有效的 优化措施,而js优化中最主要的还是对dom操作的优化,单纯的js执行时间是很短的,而js和dom之间的交互是通过接口来完成,通过接口来完成的都会 有延时,所以要尽量减少dom操作,尤其是在大循环中。
最后,总结下常见的dom操作的优化方法(节选自高性能JavaScript)
1 最小化dom访问次数,尽可能在js端执行;
2 如果需要多次访问某个dom节点,请使用局部变量存储对它的引用;
3 小心处理html集合,因为它实时连系着底层的文档,把集合的长度缓存到一个变量中,并在迭代中使用它,如果需要经常操作集合,建议把它拷贝到一个数组中;
4 如果可能的话,使用速度更快的API,比如querySelectorAll和firstElementChild;
5 要留意重绘和重排;批量修改样式时,“离线”操作dom树;使用缓存,并减少访问布局的次数;
6 动画中使用绝对定位,使用拖放代理
7 使用事件委托来减少事件处理器的数量
总结下js中对数据访问的优化(节选自高性能JavaScript)
1 函数中读写局部变量总是最快的,而全局变量的读取则是最慢的;
2 尽可能地少用with 语句,因为它会增加with 语句以外的数据的访问代价;
3 闭包尽管强大,但不可滥用,否则会影响到执行速度以及内存;
4 嵌套的对象成员会明显影响性能,尽量少用;
5 避免多次访问对象成员或函数中的全局变量,尽量将它们赋值给局部变量以缓存。
所在项目: 机票预定系统
所属模块: 前端查询模块
存在的问题: 用户查询出航班数据时,点击按照价格排序,当航班数据很多时(大于>30条),在IE浏览器下会因为消耗过多内存而崩溃
涉及的函数: 2个 reverseTr reverseTrLowToHigh
原来代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
01 function reverseTr(sortType) { 02 var sortTrLength = $( '.sortTable' ).length; 03 for ( var sortedI = sortTrLength - 1; sortedI > 0; sortedI--) { 04 var minTr = $( '.sortTable' ).eq(0); 05 var currentMin = 0; //定义当前最小值的编号用户与后面判断最终最小与最后是否相同,若同,则不变换位置(会引起删除现象) 06 for ( var sortedJ = 0; sortedJ <= sortedI; sortedJ++) { 07 var sortingTr = $( '.sortTable' ).eq(sortedJ); 08 if (parseInt(minTr.attr(sortType), '10' ) > parseInt(sortingTr.attr(sortType), '10' )) { 09 minTr = sortingTr; 10 currentMin = sortedJ; 11 } 12 } 13 if (currentMin != sortedJ - 1) { 14 var currentLastTr = $( '.sortTable' ).eq(sortedI); 15 currentLastTr.after(minTr); 16 } 17 18 } 19 var cssOdd = false ; 20 $( '.sortTable' ).each( function (){ 21 if (cssOdd){ 22 $( this ).attr( 'class' , 'odd sortTable' ); 23 cssOdd = false ; 24 } else { 25 $( this ).attr( 'class' , 'sortTable' ); 26 cssOdd = true ; 27 } 28 29 }); 30 } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
01 function reverseTrLowToHigh(sortType) { 02 var sortTrLength = $( '.sortTable' ).length; 03 04 for ( var sortedI = sortTrLength - 1; sortedI > 0; sortedI--) { 05 var minTr = $( '.sortTable' ).eq(0); 06 var currentMin = 0; //定义当前最小值的编号用户与后面判断最终最小与最后是否相同,若同,则不变换位置(会引起删除现象) 07 for ( var sortedJ = 0; sortedJ <= sortedI; sortedJ++) { 08 var sortingTr = $( '.sortTable' ).eq(sortedJ); 09 if (parseInt(minTr.attr(sortType), '10' ) < parseInt(sortingTr.attr(sortType), '10' )) { 10 minTr = sortingTr; 11 currentMin = sortedJ; 12 } 13 } 14 if (currentMin != sortedJ - 1) { 15 var currentLastTr = $( '.sortTable' ).eq(sortedI); 16 currentLastTr.after(minTr); 17 } 18 19 } 20 var cssOdd = false ; 21 $( '.sortTable' ).each( function (){ 22 if (cssOdd){ 23 $( this ).attr( 'class' , 'odd sortTable' ); 24 cssOdd = false ; 25 } else { 26 $( this ).attr( 'class' , 'sortTable' ); 27 cssOdd = true ; 28 } 29 30 }); 31 } |
注:这两个JS函数可以根据传入的 sortType 排序,sortType不仅仅是价格;reverseTr 第 20 行,设置每一行的样式
根据页面的数据量来说,JS所采用的冒泡排序算法是没有问题的,那问题出在什么地方呢?
之前我并没有碰到过JS性能的问题,SO GOOGLE…… 发现 dom的绘制和重绘非常消耗CPU和内存,dom结构和样式发生变化时都会发生重绘,在IE下尤为突出(可能是因为IE没有做相关优化,chrome快的多)。
因此改正代码,减少重绘次数;
以这个函数 reverseTr 举例: 第 15 行, 之前每次操作都会去操作dom树,都会引起dom重绘;第 20 行,遍历航班列表 设置样式的操作,也会引起dom重绘;把dom操作进行合并,也就说现在内存中进行dom操作,然后apeend到当前dom树中,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
function reverseTr(sortType) { var sortTrLength = $( '.sortTable' ).length; var temp = document.createElement( "div" ); temp.className = "tb-void" ; for ( var sortedI = sortTrLength - 1; sortedI > 0; sortedI--) { var minTr = $( '.sortTable' ).eq(0); var currentMin = 0; //定义当前最小值的编号用户与后面判断最终最小与最后是否相同,若同,则不变换位置(会引起删除现象) for ( var sortedJ = 0; sortedJ <= sortedI; sortedJ++) { var sortingTr = $( '.sortTable' ).eq(sortedJ); if (parseInt(minTr.attr(sortType), '10' ) > parseInt(sortingTr.attr(sortType), '10' )) { minTr = sortingTr; currentMin = sortedJ; } } if (currentMin != sortedJ - 1) { var currentLastTr = $( '.sortTable' ).eq(sortedI); temp.appendChild(minTr.get(0)); } } $( '.tb-void' ).empty(); var cssOdd = false ; var jtemp = $(temp); jtemp.children().each( function (){ if (cssOdd){ $( this ).attr( 'class' , 'odd sortTable' ); cssOdd = false ; } else { $( this ).attr( 'class' , 'sortTable' ); cssOdd = true ; } }); $( '.tb-void' ).replaceWith(jtemp); } |