po

有这么一个题目:找出由数字组成的数组中最大值的索引。 (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替代

 

但实际情况真的如上面所猜测的吗?下面我们来做个测试:

测试地址:

测试结果如下:

数组长度 方案一(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时浏览器抛出了最大调用堆栈的异常,我在以前的文章《Javascript之UI线程与性能优化》中也提到过,浏览器对调用堆栈的大小是有限制的。不同浏览器对函数最大参数长度的限制是不一样的,所以这里需要注意下。


高级浏览器的表现似乎比较统一,那么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》