深入探究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中this关键字(翻译+自我理解)
Oct 20 Javascript
Javascript中的几种URL编码方法比较
Jan 23 Javascript
js实现文本框选中的方法
May 26 Javascript
JavaScript实现添加、查找、删除元素
Jul 02 Javascript
JavaScript必知必会(十) call apply bind的用法说明
Jun 08 Javascript
jQuery实现边框动态效果的实例代码
Sep 23 Javascript
把JavaScript代码改成ES6语法不完全指南(分享)
Sep 10 Javascript
jQuery响应滚动条事件功能示例
Oct 14 jQuery
详解如何给React-Router添加路由页面切换时的过渡动画
Apr 25 Javascript
扫微信小程序码实现网站登陆实现解析
Aug 20 Javascript
Vue 中使用lodash对事件进行防抖和节流操作
Jul 26 Javascript
vue 在服务器端直接修改请求的接口地址
Dec 19 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
MySQL连接数超过限制的解决方法
2011/07/17 PHP
php+ajax做仿百度搜索下拉自动提示框(有实例)
2012/08/21 PHP
PHP跨时区(UTC时间)应用解决方案
2013/01/11 PHP
ThinkPHP中使用ajax接收json数据的方法
2014/12/18 PHP
php对象和数组相互转换的方法
2015/05/12 PHP
laravel执行php artisan migrate报错的解决方法
2019/10/09 PHP
浅谈javascript 面向对象编程
2009/10/28 Javascript
Javascript中正则表达式的全局匹配模式分析
2011/04/26 Javascript
禁止空格提交表单的js代码
2013/11/17 Javascript
jQuery获得document和window对象宽度和高度的方法
2015/03/25 Javascript
JavaScript中数据结构与算法(一):栈
2015/06/19 Javascript
Bootstrap菜单按钮及导航实例解析
2016/09/09 Javascript
js-FCC算法-No repeats please字符串的全排列(详解)
2017/05/02 Javascript
详谈js中标准for循环与foreach(for in)的区别
2017/11/02 Javascript
Vue组件之自定义事件的功能图解
2018/02/01 Javascript
微信小程序实现提交input信息到后台的方法示例
2019/01/19 Javascript
零基础之Node.js搭建API服务器的详解
2019/03/08 Javascript
详解JavaScript 浮点数运算的精度问题
2019/07/23 Javascript
[30:37]【全国守擂赛】第三周擂主赛 Dark Knight vs. Leopard Gaming
2020/05/04 DOTA
Python Socket实现简单TCP Server/client功能示例
2017/08/05 Python
Python实现图片转字符画的示例
2017/08/22 Python
python的中异常处理机制
2018/08/30 Python
Python搭建Spark分布式集群环境
2019/07/05 Python
python解释器spython使用及原理解析
2019/08/24 Python
Python @property原理解析和用法实例
2020/02/11 Python
HTML5 Canvas阴影使用方法实例演示
2013/08/02 HTML / CSS
星空联盟C# .net笔试题
2014/12/05 面试题
Ruby如何创建一个线程
2013/03/10 面试题
普通大学毕业生自荐信范文
2014/02/23 职场文书
委托证明模板
2014/09/16 职场文书
学校四风问题对照检查材料思想汇报
2014/09/26 职场文书
购房个人委托书范本
2014/10/11 职场文书
2015秋季幼儿园开学寄语
2015/03/25 职场文书
Go各时间字符串使用解析
2021/04/02 Golang
Go 语言下基于Redis分布式锁的实现方式
2021/06/28 Golang
Python中使用Opencv开发停车位计数器功能
2022/04/04 Python