与IE兼容性纠缠的这几个月(续)

前段时间写了篇博客,简单描述了之前参与的系统中解决IE兼容性问题的一点过程。对于我个人而言,是非常厌恶微软IE789浏览器的,经常和同事开玩笑说,“IE789简直就是阻止人类进步的绊脚石”。我们这个系统从今年3月份正式上线,一直到现在,还在处于天天救火严重bug的状态,这其中包含了所有你能想出来的原因,当然之一就是IE兼容性问题。

当然,不可否认,在解决IE兼容性问题的过程中,确实变相找到一些乐趣。至少比重复写着controller,service,dao,onclick,$().val()来的有意思。加上,这周中,用我上一篇文章中提到的一点,帮助了同事解决了迫在眉睫的问题,觉得还是有意义的。

今天,要得瑟的内容,层面上,要比上一篇更高一个层次,就是性能问题。性能问题,可以说和ie无关,也可以说有关。比如你无端跑了极大量的循环,无论哪个浏览器里,都是不应该的。当然,在chrome里,可能是一瞬间,在ie里,却需要N秒。所以,又是和IE有关。

以下是三个Case:

1.对HandsonTable单元格赋值

handsonTable是一个国外的开源工具,用来提供仿excel的js表格控件,包括合并单元格等特性,是非常优秀的工具。

我们的系统,打开某个页面的时候,非常慢,在chrome下也需要2秒+的样子,在IE9下需要10秒,IE8下需要14秒+,基本就是不可忍了。于是,我尝试着去解决这个性能问题。首先,当然是使用工具检测一下,在哪些代码上可能存在瓶颈。我选择的是chrome的profiles工具。有些人可能会觉得在chrome下测比较难,因为chrome跑js性能太好。但是,问题代码和正常代码的耗时比例,其实差别不会太大。我使用IE9的profiles测试过,心里是有底的。而且我在mac的虚拟机里跑一个ie9,加上win7在高分屏下的体验,总之就是不愿意。。

通过profiles对js方法运行时间的统计,通过tree结构的结果列表分析,最终定位于handsontable的一个对单元格赋值的API。

这里要插一句,定位到问题所在的过程,并非一帆风顺。不像自己写的代码,出了异常,能直接定位到某一行的某个变量。前端js里大多数都是第三方库,自己写的出问题的几率很少,很大程度上,是自己没用好第三方库的api,导致问题爆发在第三方库的逻辑中,而这些逻辑往往比较抽象。总之,依靠缩小范围,推理,猜测,动用一切经验和智慧,找出关键点。

handsontable的这个api,setDataAtCell,很明显是给一个单元格赋值,再看我们使用的逻辑,是个使用了两层for循环给一个表格赋上初始值。当时就明白了,很明显,开发的过程中没想到实际使用时会出现上百列的场景,完全没想到这么多列下,一个一个赋值会慢的这么明显。

解决的办法,我第一反应就是,批量赋值。尽管当时我完全不了解handsontable的api,但是我心里想,肯定有批量赋值的api,不然它就是shit!而且批量赋值的操作在底层一定不会像我们这样循环去做,不然还是shit!打开官网,粗粗扫了一遍api文档,操,没找到……然后开始细细地看,忽然发现setDataAtCell方法居然还可以传数组!由于官网文档写的实在太简陋,足足体会了好一会,才认定,一定就是它了。

于是,我把双层for循环改成单层for循环。其实就是,一行一行赋值,而不是一个一个单元格赋值。耗时瞬减!IE下足足快了5秒。

解决完这个问题,感觉还是很良好的,而且解决思路简单,没有副作用。但是下面这个case,就有点麻烦了。

2.jQueryUI的sortable组件

紧跟着上一个case,从原来10s+减少到5s+,已经提升了一倍性能,但是还不够,没有人愿意等5秒钟才打开页面。

我本想说,用ie89的人活该,但是想想他们也是无辜的,因为有些老系统只支持老浏览器,总不能让用户一天到晚切换浏览器吧。这就是个恶心循环!

接着说,依然使用profiles检测,可以看见前面setData部分的代码运行耗时比例已经降低很多,原来可是达到了70%+。此时另一个堆栈挑起了性能慢的大梁。通过tree结构分析,似乎涉及到jQueryUI的代码,但那时不了解代码逻辑,也就不知道为什么慢在jQueryUI上了。主要是我不知道sortable和dragglable是属于jQueryUI的,囧。。

忽然想到,console.time()方法,可以测量代码执行时间,于是我在出问题的方法里,切了好几段,分别统计执行时间。运行,很明显,在创建sortable对像的方法上,消耗了大量的时间!问题点找到了,开始读逻辑,才发现原来给上文表格中的每一个单元格,都绑定了一个sortable组件。因为交互逻辑是,可以拖拽一些东西到表格内。

google了一下,全世界都在抱怨sortable组件创建时的消耗大。当时一想,坏了,要是设计就必须放这个组件,就不能随意砍掉,而且几乎没有优化余地啊。

此时,走了一个弯路,由于是失败的弯路,准备放在最后讲。

读通原有设计思路是不可避免得了,耐下心读吧。同时还去官网学习了我不熟悉的sortable,dragglable,droppable组件的使用。然后,惊!天!大!发!现!

先科普一下,sortable的作用是,某个区域内的元素,可拖动交换位置,比如一个列表中的每一行。draggable定义了可拖动元素,而droppable是指该区域可被放置拖动的元素。因此sortable就好比,draggable和droppable的结合。

页面逻辑是,用户从其他地方拖动一个元素到表格的一个单元格内,再拖动一个新元素进来时,会覆盖。然后顿时震惊了,现在将单元格本身作为一个sortable区域,而内部却永远只需要存在一个元素,就完全没有必要成为sortable区域啊,又不需要排序!于是将sortable组件改为droppable组件的想法自然产生。

由于我也不知道droppable的性能会被sortable好多少,所以怀着惴惴不安的心情做了尝试,中间省略很长时间调节代码逻辑的过程,这也不是容易的过程。。。

确实快了很多!之前IE9下降到5秒+的过程,如今只需要2秒了!换句话说,两个优化以后,ie9从10秒降到2秒,同时,ie8也从14秒降到4秒。

至此,这部分就不准备再调节了,虽然理论上还存在优化的余地。

3.jquery选择器:not

这个Case准备简单掠过,因为直接砍掉了一部分逻辑,不算真正改好,(其实我觉得原逻辑也不好,所以砍掉就砍掉了。。。。嘿嘿偷偷的)

通过profiles和console.time定位到了一个jquery选择器居然执行了好几秒,这里含有多个选择器的并列,还涉及到not语法,通过调试,发现单个使用not时没有性能问题,但与其他并列选择器的同时使用,就出奇的慢。关于这个我就没有深究,心想这not这种东西就和sql里not,like之流有异曲同工之妙,总有慢的理由。。。。遂,砍之。。

到这里,经历过的三次调优就结束了,之后由于参与其他任务,没时间调优,所以就放置一边了。总之,和前一篇博客一样,记一件有意义的事情。

——————————————————————————————————

前面讲过一个弯路,虽是弯路,但是挺有意思的尝试。

由于sortable慢,一开始又不知道可以怎么优化。于是想着能不能将这种卡顿感弱化。js是一个单线程的语言,放在浏览器里就是,如果一直在忙于执行一段任务,其他事情就会被卡住,包括用户页面操作,页面无响应,弹出IE是否结束脚本警告框,等等。

那么如果我自己模拟一个时分复用,将5秒钟的任务分割成N分,每5/N秒中断一次,因为有了中断,js线程就不会永远卡在当前任务上,有机会让其他任务插一脚执行,比如页面点个按钮啥的。在stackoverflow上拷了段js代码,用promise辅以setTimeout,模拟出一个时分复用的样子。

当然结果还是不理想啦,感觉卡顿感更严重了啊,页面点击时好时坏的,想着不能从根本上解决,还是不行啊,于是只能开始啃上下文代码。。。

——————————————————————————————————

相关推荐