深入探究JavaScript中for循环的效率问题及相关优化


Posted in Javascript onMarch 13, 2016

Underscore.js库

你一天(一周)内写了多少个循环了?

var i;
for(i = 0; i < someArray.length; i++) {
 var someThing = someArray[i];
 doSomeWorkOn(someThing);
}

这当然无害,但这种写法非常丑而且奇怪,这也不是真正需要抱怨的。但这种写法太平庸了。

var i,
 j;
for(i = 0; i < someArray.length; i++) {
 var someThing = someArray[i];
 for(j = 0; j < someThing.stuff.length; j++) {
   doSomeWorkOn(someThing.stuff[j]);
 }
}

你在扩展糟糕的代码,在你抛出一大堆if前,你已经精神错乱了。
我在两年里没有写一个循环(loop)。
“你在说什么?”
这是真的,一个冷笑话。其实不是一个都没有(好吧,我确实写了几个),因为我不写循环(loops),我的代码更容易理解。
怎么做的呢?

_.each(someArray, function(someThing) {
 doSomeWorkOn(someThing);
})

或者更好一点:

_.each(someArray, doSomeWorkOn);

这就是underscorejs所做到的。干净,简单,易读,短,没有中间变量,没有成堆的分号,简单非常优雅。
这是另外一些例子。

var i,
 result = [];
for(i = 0; i < someArray.length; i++) {
 var someThing = someArray[i];
 // 打到这,我已经手疼了
 if(someThing.isAwesome === true) {
   result.push(someArray[i]);
 }
}

同样,一个使用循环浪费时间的典型用例。即便这些网站是宣传禁烟和素食主义的,看到这些代码我也感到义愤。看看简单的写法。

var result = _.filter(someArray, function(someThing) {
 return someThing.isAwesome === true;
})

像underscore中的filter(过滤)的名字那样,随手写的3行代码就可以给你一个新的数组(array)。
或者你想把这些数组转换成另外一种形式?

var result = _.map(someArray, function(someThing) {
 return trasformTheThing(someThing);
})

上面三个例子在日常生活中已经够用了,但这些功能还不足矣让underscore放到台面上。

var grandTotal = 0,
 somePercentage = 1.07,
 severalNumbers = [33, 54, 42],
 i; // don't forget to hoist those indices;
for(i = 0; i < severalNumbers.length; i++) {
 var aNumber = severalNumbers[i];
 grandTotal += aNumber * somePercentage;
}

underscore版本

var somePercentage = 1.07,
 severalNumbers = [33, 54, 42],
 grandTotal;
grandTotal = _.reduce(severalNumbers, function(runningTotal, aNumber) {
 return runningTotal + (aNumber * somePercentage);
}, 0)

这个刚开始看上去可能有点怪,我查了下关于reduce的文档,知道了它的存在。因为我拒绝使用循环,所以它是我的首选。上面这些东西仅仅是入门,underscorejs库还有一大堆牛B的功能。

30天不使用循环的挑战。

在一下一个30天里,不要使用任何循环,如果你看到一堆讨厌和粗糙的东西,用each或者map将他们替换掉。再用一点reducing。

你需要注意到,Underscore是通往函数式编程的。一种看得见,看不见的方式。一条很好的途径。

OurJS注*目前现代浏览器已经支持each, filter, map, reduce方法,但underscore库可以实现对旧版IE的兼容,下面是使用ES5原生方法写的例子:

[3,4,5,3,3].forEach(function(obj){
  console.log(obj);
});

[1,2,3,4,5].filter(function(obj){
  return obj < 3
});

[9,8,5,2,3,4,5].map(function(obj){
  return obj + 2;
});

[1,2,3,4,5].reduce(function(pre, cur, idx, arr) {
  console.log(idx);  //4 个循环: 2-5
  return pre + cur;
});  //15

//sort方法同样很有用
[9,8,5,2,3,4,5].sort(function(obj1, obj2){
  return obj1 - obj2;
});

for in与for loop

有人提出for in的效率要比for loop(循环)的效率低非常多。现在我们测试一下在不同浏览器中使用for in, for loop和forEach在处理大数组时的效率究竟如何。

目前绝大部分开源软件都会在for loop中缓存数组长度,因为普通观点认为某些浏览器Array.length每次都会重新计算数组长度,因此通常用临时变量来事先存储数组长度,如:

for (var idx = 0, len = testArray.length; idx < len; idx++) {
 //do sth.
}

我们也会测试一下缓存与不缓存时的性能差异。

同时在每个测试循环中添加求和运算,来表明其不是空循环。

for (var idx = 0, len = testArray.length; idx < len; idx++) {
 //do sth.
}

我们也会测试一下缓存与不缓存时的性能差异。

同时在每个测试循环中添加求和运算,来表明其不是空循环。

测试代码如下,点击运行即可查看
HTML 代码

<h4 id="browser"></h4>
<table id="results" class="table"></table>

JavaScript 代码

function () {

 //准备测试数据, 有200万条数据的大数组
 var testArray = []
  , testObject = {}
  , idx
  , len = 2000000
  , tmp = 0
  , $results = $("#results")
  , $browser = $("#browser")
  ;

 $browser.html(navigator.userAgent);
 $results.html('');

 for (var i = 0; i < len; i++) {
  var number = Math.random(); //若希望加快运算速度可使用取整:Math.random() * 10 | 0
  testArray.push(number);
  testObject[i] = number;
 }

 $results.append('<tr><th>测试代码</th><th>计算结果</th><th>所需时间,毫秒</th></tr>');

 //测试函数
 var test = function(testFunc) {
  var startTime
   , endTime
   , result
   ;

  startTime = new Date();
  tmp = 0;
  testFunc();
  endTime  = new Date();

  //计算测试用例(Test Case)运行所需要的时间
  result = endTime - startTime;
  $results.append('<tr><td><pre>{0}</pre></td><td>{1}</td><td>{2}</td></tr>'.format(testFunc.toString(), tmp | 0, result));
 };


 test(function() {
  //测试for in 的效率
  for (idx in testArray) {
   tmp += testArray[idx]; //经测试,idx是string类型,可能是慢的原因之一
  }
 });

 test(function() {
  //测试for loop循环的效率
  for (idx = 0, len = testArray.length; idx < len; idx++) {
   tmp += testArray[idx];
  }
 });

 test(function() {
  //测试forEach的效率
  testArray.forEach(function(data) {
   tmp += data;
  });
 });

 test(function() {
  //测试不缓存Array.length时效率
  for (idx = 0; idx < testArray.length; idx++) {
   tmp += testArray[idx];
  }
 });

 test(function() {
  //测试使用{} (Object) 存健值对时,使用for in的效率如何
  for (idx in testObject) {
   tmp += testObject[idx];
  }
 });
 
 test(function() {
  //测试从{} Object查值时的效率如何(这里的健key值事先己知)
  for (idx = 0, len = testArray.length; idx < len; idx++) {
   tmp += testObject[idx];
  }
 });

}

运行 [需稍等片刻]
测试结果
测试结果可能因计算而异,这是在我机器上运行用,Firefox, Chrome, IE三者测试结果拼接的一张汇总。

深入探究JavaScript中for循环的效率问题及相关优化

以下是几个观察到的结论

  • for in比for loop慢非常多,在Chrome中至少慢20倍
  • FF对forEach(ES5)做了优化,性能比for loop还要好一点,但Chrome/IEn性能均较差
  • FF/Chrome缓存Array.length均比直接用时要慢一点。除IE最新版缓存后性能提升微乎其微(这一点非常意外)
  • 在某些情况下,FF的JS引擎性能似乎比V8要好些
Javascript 相关文章推荐
深入理解JavaScript系列(12) 变量对象(Variable Object)
Jan 16 Javascript
jQuery操作属性和样式详解
Apr 13 Javascript
javascript实现简易计算器的代码
May 31 Javascript
canvas实现图像放大镜
Feb 06 Javascript
JS和JQuery实现雪花飘落效果
Nov 30 jQuery
基于jQuery.i18n实现web前端的国际化
May 04 jQuery
angular 实时监听input框value值的变化触发函数方法
Aug 31 Javascript
vue-cli 引入jQuery,Bootstrap,popper的方法
Sep 03 jQuery
Vue.js 中 axios 跨域访问错误问题及解决方法
Nov 21 Javascript
过滤器vue.filters的使用方法实现
Sep 18 Javascript
在vue中利用v-html按分号将文本换行的例子
Nov 14 Javascript
如何优化vue打包文件过大
Apr 13 Vue.js
简单谈谈json跨域
Mar 13 #Javascript
详解JavaScript中数组和字符串的lastIndexOf()方法使用
Mar 13 #Javascript
Node.js编写爬虫的基本思路及抓取百度图片的实例分享
Mar 12 #Javascript
JavaScript中循环遍历Array与Map的方法小结
Mar 12 #Javascript
Node.js的Express框架使用上手指南
Mar 12 #Javascript
Node.js项目中调用JavaScript的EJS模板库的方法
Mar 11 #Javascript
JavaScript操作HTML DOM节点的基础教程
Mar 11 #Javascript
You might like
PHP中include()与require()的区别说明
2010/03/10 PHP
php获取qq用户昵称和在线状态(实例分析)
2013/10/27 PHP
使用 PHPStorm 开发 Laravel
2015/03/24 PHP
javascript获取设置div的高度和宽度兼容任何浏览器
2013/09/22 Javascript
JS获取各种宽度、高度的简单介绍
2014/12/19 Javascript
javascript事件冒泡实例分析
2015/05/13 Javascript
JavaScript中数据结构与算法(一):栈
2015/06/19 Javascript
jQuery+css实现的蓝色水平二级导航菜单效果代码
2015/09/11 Javascript
浅谈$('div a') 与$('div&gt;a')的区别
2016/07/18 Javascript
原生javascript实现图片放大镜效果
2017/01/18 Javascript
Vue实现typeahead组件功能(非常靠谱)
2017/08/26 Javascript
对angularJs中$sce服务安全显示html文本的实例
2018/09/30 Javascript
javascript刷新父页面方法汇总详解
2019/10/10 Javascript
vue + el-form 实现的多层循环表单验证
2020/11/25 Vue.js
[47:39]2018DOTA2亚洲邀请赛 3.31 小组赛 A组 LGD vs OPTIC
2018/03/31 DOTA
浅谈python中的面向对象和类的基本语法
2016/06/13 Python
判断网页编码的方法python版
2016/08/12 Python
Python 中开发pattern的string模板(template) 实例详解
2017/04/01 Python
Python八大常见排序算法定义、实现及时间消耗效率分析
2018/04/27 Python
用python生成1000个txt文件的方法
2018/10/25 Python
Django Sitemap 站点地图的实现方法
2019/04/29 Python
Python列表与元组的异同详解
2019/07/02 Python
PyQt5高级界面控件之QTableWidget的具体使用方法
2020/02/23 Python
TensorFLow 数学运算的示例代码
2020/04/21 Python
基于Python爬取搜狐证券股票过程解析
2020/11/18 Python
Python 实现键盘鼠标按键模拟
2020/11/18 Python
使用css3实现的windows8开机加载动画
2014/12/09 HTML / CSS
全球最大的游戏市场:G2A
2018/07/05 全球购物
继电保护工岗位职责
2014/01/05 职场文书
见习期自我鉴定
2014/01/31 职场文书
荷叶圆圆教学反思
2014/02/01 职场文书
难忘的一天教学反思
2014/04/30 职场文书
物理教育专业求职信
2014/06/25 职场文书
学校师德师风整改方案
2014/10/28 职场文书
2015年银行员工工作总结
2015/04/24 职场文书
《揠苗助长》教学反思
2016/02/20 职场文书