优化系统js小记
做了CRM一段时间,实在沉闷远大于乐趣。 虽然其中有些工作有挑战性,能学到不少东西,但也有不少时间在研究需求。
是通过操作另一个系统,来发现业务需求。 太坑爹了。
今天无法继续忍受,所以开始尝试优化一下系统的js代码。
此文记录优化过程中的少许收获。
PS: 由于公司系统只兼容IE,所以我是用IE 8探查器来测试占用时间的。 此外,为了测试一个js方法的性能,写了一个简单的工具用于测试。不再敖述,入正题。
1. js方法的arguments转换成数组
众所周知,js方法中可以用arguments访问参数数组。 但arguments虽然数据形式是数组,却不具备Array对象的众多方法。直接使用的话会诸多不便。
而jquery提供了makeArray方法,使用该方法可以把arguments转换成数组对象,代码如下:
1 $.makeArray(arguments)
但是,这个方法虽然方便,性能却不高。所以也可以用原生js方法转换,代码如下:
Array.prototype.slice.call(arguments)
通过call Array对象里的slice方法实现。
call的说明具体可查看https://developer.mozilla.org/en/docs/JavaScript/Reference/Global_Objects/Function/call
slice方法支持2个参数,具体可查看http://www.w3schools.com/jsref/jsref_slice_array.asp
经过这个改动,原本的函数执行10000次的时间减少了100ms左右。
2. for循环中的length
for循环相信大家都是熟悉得不能再熟悉的了。请看以下代码:
1 var divs = document.getElementsByTagName("div"); 2 for(var i =0; i<divs.length;i++) 3 { 4 // do something 5 }
相信这个是正常得不能在正常的写法了。 在我们系统中,一个加载首页内容的方法中,也有类似的写法。
首页整个加载方法需要执行350ms-400ms。我在逐步排除代码后。发现一次空的for循环需要执行90ms。
是的。是空的,我把for循环中实际的代码都注释后,也就是类似上面的实例代码。
然后我想起之前在网上看过一种建议,就是先把length缓存起来。于是我改成了这样:
1 var divs = document.getElementsByTagName("div"); 2 var divCount = divs.length; 3 for(var i =0;i<divCount;i++) 4 { 5 // do something 6 }
测试结果。加载方法时间编程了220-250ms。
我自己的分析是大概是: js的变量分别存放在栈和堆中。在栈中检索变量的速度远大于堆。而方法中的局部变量,貌似是存放在栈中的,所以检索速度很快。
原本divs数组应该是存放与栈中的,但是divs的length属性并不是捆绑着和divs存放在一起的,可能divs中捆绑着所有属性(包括事件)的索引(可能是指针或其他形式的东西)。当需要访问这些属性时,会使用索引找到属性实际保存的内存中的位置查找,最坏的情况是这个属性值被保存在堆中了。
这就导致for循环中,每次要查找divs.length时,都要经过2个阶段的查找,先找到divs的length索引,再用索引到内存中查找length的值。这就是导致上一种写法速度如此慢的原因了。
而把divs.length先缓存在局部变量中,相当于在栈中创建了一个变量(检索速度飞快),每次获取这个值的速度远远大于获取divs.length的速度。所以下一种写法的速度就会比较快了。
以上均为个人猜测。欢迎指正。
3. 字符串拼接
大多数人都知道,字符串用+号拼接会消耗较多资源,因为每个加号拼接的字符串实际上都会被当成一个字符串变量单独分配内存(和C#类似)。当要用+号拼接的次数越多时,占用内存越多。而且拼接操作也会消耗一定资源。 所以我在代码中将绝大部分的用+号拼接的字符串改成用Array对象的push方法拼接。最后返回join("")方法的结果。代码如下:
1 // 原代码 2 var str = "1"+"2"+"3"; 3 return str; 4 5 // 改成 6 var str = []; 7 str.push("1","2","3"); // push方法接受多个参数,会依次push到数组尾部。 8 return str.join("") 9 10 11 // 以上两种方法的结果都是123,但当拼接次数增加时,后一种的性能优势会越来越明显。