有这么一个题目:找出由数字组成的数组中最大值的索引。 (PS:不用考虑兼容性)
- 方案一:使用for循环遍历+条件判断
- function indexOfMax(arr) {
- var maxIndex = 0;
- for (var i = 1, len = arr.length; i < len; i++) {
- if (arr[i] > arr[maxIndex]) {
- maxIndex = i;
- }
- }
- return maxIndex;
- }
- 方案二:使用Array对象内置的reduce函数+条件判断
- function indexOfMax(arr) {
- return arr.reduce(function(max, next,index) {
- return next > arr[max] ? index : max;
- }, 0);
- }
- 方案三:使用Array对象内置的indexOf函数+Math.max方法
- function indexOfMax(arr) {
- return arr.indexOf(Math.max.apply(Math, arr));
- }
三个方案中,方案一最传统最直接明了,相信也是大部分人脑海里最早浮现出来的方案;方案二比方案一更简洁,使用reduce方法替换了for循环;方案三最简洁,没有循环,没有条件判断,一行代码解决问题。大部分人都喜欢追求代码简洁优雅,要是可选的话,相信很多人都会选择方案三。
但问题来了,这三个方案中哪一个性能最好呢?
- 方案一:性能最差,因为它需要手动去遍历数组,并且每个遍历都需要进行条件判断,所以这里的性能损耗最大
- 方案二:性能较好,因为它使用了JavaScript内置的reduce函数帮我们完成了方案一中手动完成的数组遍历工作;JavaScript内置的方法肯定是比我们自己实现的要快,因此在同样需要条件判断的情况下,方案二肯定是由于方案一的
- 方案三:性能最好,因为相对方案二,它连条件判断都省了,使用了Math.max替代
但实际情况真的如上面所猜测的吗?下面我们来做个测试:
测试地址:
- indexOfMax-100,000 https://jsperf.com/indexofmax
- indexOfMax-120,000 https://jsperf.com/indexofmax-120000
- indexOfMax-130,000 https://jsperf.com/indexofmax-130000
测试结果如下:
数组长度 | 方案一(for) | 方案二(reduce) | 方案三(indexOf、max) |
Chrome (35.0.1916.114 m) | |||
100,000 | 5,016 ±0.40% fastest |
128 ±0.87% 97% slower |
610 ±0.27% 88% slower |
120,000 | 4,194 ±0.40% fastest |
104 ±1.16% 98% slower |
498 ±0.36% 88% slower |
130,000 | 3,830 ±0.46% fastest |
93.89 ±1.13% 98% slower |
ERROR (RangeError: Maximum call stack size exceeded.) |
Firefox (29.0.1) | |||
100,000 | 6,191 ±2.06% fastest |
280 ±12.22% 96% slower |
675 ±1.29% 89% slower |
120,000 | 5,115 ±1.44% fastest |
237 ±12.66% 96% slower |
582 ±1.78% 89% slower |
130,000 | 5,094 ±2.40% fastest |
216 ±12.49% 96% slower |
528 ±1.19% 90% slower |
从结果中可以看得出来,性能上:方案一最好,方案三次之,方案二最差;与上面猜测的结果完全相反。方案一最好猜测应该是浏览器JavaScript解释引擎对代码进行了优化后执行的结果,优化后的代码从底层实现上来看应该是比reduce、indexOf、Math.max等底层接口的性能更好,因此效率更高。 同时注意到,在Chrome下,当数组长度达到130,000时浏览器抛出了最大调用堆栈的异常,浏览器对调用堆栈的大小是有限制的。不同浏览器对函数最大参数长度的限制是不一样的,所以这里需要注意下。
高级浏览器的表现似乎比较统一,那么IE的表现又怎么样呢?
IE下测试结果如下:
数组长度 | 方案一(for) | 方案二(reduce) | 方案三(indexOf、max) |
IE9 | |||
100,000 | 214 ±0.34% 57% slower |
495 ±0.54% fastest |
235 ±0.33% 52% slower |
120,000 | 179 ±0.29% 57% slower |
416 ±0.45% fastest |
197 ±0.49% 53% slower |
130,000 | 166 ±0.38% 57% slower |
382 ±0.67% fastest |
179 ±0.37% 53% slower |
IE10 | |||
100,000 | 2,343 ±0.87% fastest |
499 ±0.95% 79% slower |
314 ±0.61% 87% slower |
120,000 | 1,823 ±1.01% fastest |
413 ±1.79% 78% slower |
257 ±1.33% 86% slower |
130,000 | 1,630 ±0.93% fastest |
385 ±0.93% 76% slower |
241 ±0.63% 85% slower |
IE11 | |||
100,000 | 2,994 ±1.91% fastest |
376 ±1.11% 87% slower |
206 ±0.90% 93% slower |
120,000 | 2,451 ±1.70% fastest |
306 ±2.90% 88% slower |
170 ±1.38% 93% slower |
130,000 | 1,934 ±11.64% fastest |
286 ±1.66% 84% slower |
156 ±1.20% 91% slower |
由于IE10、IE11越发往标准浏览器靠拢,他们的表现跟IE9不同,倒与Chrome、Firefox有点类似,也是方案一最快,但是方案三最慢。这可能跟每个浏览器自身的实现以及内部优化有关系。
So,做性能优化时的几点建议:
- 内置的函数不一定是效率最好的,最简洁优雅的写法不一定可以带来性能上的提升;
- JavaScript虽然是解释型语言,但并不代表所做的操作越少性能越好;
- 要考虑不同平台以及浏览器对接口的性能差异,按需权衡;
- 尽可能保持简单的思考方式,不要过度设计,当发现性能问题时再尝试去寻找解决方案
- 性能优化需要数据支持,不能盲目相信经验或者固有认知
参考:《Find the index of the smallest element in a JavaScript array》
转载请注明出处:https://stgod.com/669/