js性能小贴士——优化循环
发布在刀刀的实践记录2015年7月25日view:2117JavaScript性能优化
在文章任何区域双击击即可给文章添加【评注】!浮到评注点上可以查看详情。

减少访问属性

场景如下:实习生小D领到了这样一个任务,需要将一个数组里的元素均除以1000。这个需求太简单了,小D拿起键盘就开始撸码,代码如下:

for (var i = 0; i < arr.length; i++) {
    arr[i] = arr[i] / 1000;
}

小D迅速完成了任务,提交代码后,主管说让他再看看是否有优化的空间。

小D开始用长度为100的数组进行测试,发现运行时间基本为0,于是果断生成了一个100万的随机数组进行测试,发现跑这段代码的时间,也并没有很高。再次和主管交流后,主管让他按照这个思路,去再写一段生成html的代码,生成10000个节点,然后使用DOM接口查询出这些节点,并把其文本节点的值设为“hehe”,再计算一下时间,小D代码如下:

var timeBefore = new Date().getTime();
var els = document.getElementsByClassName('test');
for (var i = 0; i < els.length; i++) {
    els[i].innerText = 'hehe';
}
var timeAfter = new Date().getTime();
console.log(timeAfter - timeBefore);

发现当节点数在10000时,js的运行速度就很慢了,时间基本在2000ms之上,在这个场景下,对于循环的优化就很重要了。

我们都知道js中使用DOM接口查询出的节点数组其实不等同于js中的Array,而是一个nodelist,这个nodelist是一个动态改变的对象,我们每次访问nodelist,浏览器其实都要再次进行一次查询,在for循环中使用“i < els.length”,nodelist的查询时间也会随着节点数的增多而随之增加,正是性能问题所在!

如果我们不需要对nodelist对象采取任何增删操作,我们完全可以这样写这段代码:

for (var i = 0, len = els.length; i < len; i++) {
    els[i].innerText = 'hehe';
}

一开始就用一个变量,保存nodelist的长度,此时再输出运行时间,发现执行时间已经降到了900-1000ms,整整降低了一半。

并且如果浏览器支持ECMAScript5,我们还可以使用forEach迭代器去实现该需求,性能与上一种方法差不多,代码如下:

Array.prototype.forEach.call(els, function (item) {
    item.innerText = 'hehe';
});

于是,我们可以得出结论,在进行较大数据量的循环时,要减少访问属性的次数。

减少DOM操作

小D学会了新的优化小技巧之后,感觉整个人都萌萌哒了,马上又开始了一个新的开发任务,这是一个统计任务,要求将100个节点的文本节点值相加后输出到一个id为console的节点中。这简直太应景了,小D马上使用新的小技巧,撸出代码如下:

var els = document.getElementsByClassName('test'),
    consoleEl = document.getElementById('console');
for (var i = 0, len = els.length; i < len; i++) {
    consoleEl.innerText = parseInt(consoleEl.innerText) + parseInt(els[i].innerText);
}

提交代码后,主管说,你把节点改为10000个试试看,小D照做后,立即感觉不会再爱了,因为浏览器卡死了…

主管静静地将小D的代码改为如下形式:

var total = 0;
for (var i = 0, len = els.length; i < len; i++) {
    total += parseInt(els[i].innerText);
}
consoleEl.innerText = total;

小D再运行了一下该段代码,简直是天壤之别啊,运行时间只有100多ms,因为只进行了一次DOM操作。

可见,DOM操作对性能的影响有多大,我们更应该避免在循环中操作DOM,实质上来说,我们要在循环中消除不必要的存储器引用。

缩小作用域

小D将主管修改后的代码提交上之后,结果又没通过review,小D十分纳闷,这可是主管亲手写的代码啊。再三犹豫,硬着头皮询问主管。 主管抛给他这样一段代码,让他检验执行时间:

for (var i = 0; i < 100000000; i++) {
    var a = Math.sqrt(Math.exp(i));
}

小D新建了一个文件,将该段代码拷了进去,发现执行时间高达1000多ms。主管呵呵一笑,说你把它放在一个函数里,再跑跑试试看。执行结果让小D吓尿了,只有80-90ms。

其实原因在于,直接在最外部执行的那段代码,声明的变量均为全局变量,根据javascript的作用域链的原理,每次查询i均要进行一次全局查找,如果在嵌套较深的函数中访问全局变量,执行查找的成本将会更高,所以我们一定要尽量减少访问全局变量的次数。将最外部执行的代码放入函数中后,声明的变量则均属于局部变量,所以在该函数的局部环境中的变量对象上可以搜索到该变量,性能开销自然小的多。

其它优化

小D虽然被主管鄙视了一番,但学习到了许多姿势,感觉B格又提升了不少,并且通过请主管吃饭又学习到了如循环展开,函数节流等各种奇技淫巧,度过了开心的一周。

完!

评论
发表评论
2年前

支持

2年前

支持

WRITTEN BY
叶刀刀刀刀刀
关注前端架构,工程化,nodejs初级粉丝
TA的新浪微博
PUBLISHED IN
刀刀的实践记录

记录自己在开发中的实践心得

我的收藏