貌似前端圈一直以来流传着一种误解:前端用不到算法知识。长久以来,大家或许都曾受这种说法的影响。直到前阵子遇到一个产品需求,回过头来看,发现事实并非如此。
前端排序
前端排序的场景
前端将排序条件作为请求参数传递给后端,后端将排序结果作为请求响应返回前端,这是一种常见设计。但是对于有些产品则不是那么适用。
试想一个场景:你在使用美食类APP时,是否会经常切换排序方式,一会儿按照价格排序,一会儿按照评分排序。
实际生产中,受限于服务器成本等因素,当单次数据查询成为整体性能瓶颈时,也会考虑通过将排序在前端完成的方式来优化性能。
排序算法
感觉这个没什么介绍的必要,作为计算机科学的一种基础算法,描述就直接照搬 Wikipedia。
这里存在这一段内容纯粹是为了承(cou)上(man)启(zi)下(shu)。
JavaScript的排序
既然说到前端排序,自然首先会想到JavaScript的原生接口 Array.prototype.sort。
这个接口自 ECMAScript 1st Edition 起就存在。让我们看看最新的规范中关于它的描述是什么样的。
Array.prototype.sort规范
Array.prototype.sort(compareFn)
复制代码 代码如下:
The elements of this array are sorted. The sort is not necessarily stable (that is, elements that compare equal do not necessarily remain in their original order). If comparefn is not undefined, it should be a function that accepts two arguments x and y and returns a negative value if x < y, zero if x = y, or a positive value if x > y.
显然,规范并没有限定 sort 内部实现的排序算法是什么。甚至接口的实现都不需要是 稳定排序 的。这一点很关键,接下来会多次涉及。
在这样的背景下,前端排序这件事其实取决于各家浏览器的具体实现。那么,当今主流的浏览器关于排序是怎么实现的呢?接下来,我们分别简单对比一下 Chrome、Firefox 和 Microsoft Edge。
Chrome中的实现
Chrome 的JavaScript引擎是 v8。由于它是开源的,所以可以直接看源代码。
整个 array.js 都是用 JavaScript 语言实现的。排序方法部分很明显比曾经看到过的快速排序要复杂得多,但显然核心算法还是 快速排序。算法复杂的原因在于 v8 出于性能考虑进行了很多优化。(接下来会展开说)
Firefox中的实现
暂时无法确定Firefox的JavaScript引擎即将使用的数组排序算法会是什么。[3]
按照现有的信息,SpiderMoney 内部实现了 归并排序。
Microsoft Edge中的实现
Microsoft Edge 的JavaScript引擎 Chakra 的核心部分代码已经于2016年初在Github开源。
通过看源代码可以发现,Chakra 的数组排序算法实现的也是 快速排序。而且相比较于 v8,它就只是实现了纯粹的快速排序,完全没有 v8 中的那些性能优化的踪影。
JavaScript数组排序的问题
众所周知,快速排序 是一种不稳定的排序算法,而 归并排序 是一种稳定的排序算法。由于不同引擎在算法选择上的差异,导致前端依赖 Array.prototype.sort 接口实现的JavaScript代码,在浏览器中实际执行的排序效果是不一致的。
排序稳定性的差异需要有特定的场景触发才会存在问题;在很多情况下,不稳定的排序也不会造成影响。
假如实际项目开发中,对于数组的排序没有稳定性的需求,那么其实看到这里为止即可,浏览器之间的实现差异不那么重要。
但是若项目要求排序必须是稳定的,那么这些差异的存在将无法满足需求。我们需要为此进行一些额外的工作。
举个例子:
某市的机动车牌照拍卖系统,最终中标的规则为:
1. 按价格进行倒排序;
2. 相同价格则按照竞标顺位(即价格提交时间)进行正排序。
排序若是在前端进行,那么采用快速排序的浏览器中显示的中标者很可能是不符合预期的
探究差异的背后
寻找解决办法之前,我们有必要先探究一下出现问题的原因。
Chrome为什么采用快速排序
其实这个情况从一开始便存在。
Chrome测试版 于2008年9月2日发布,然而发布后不久,就有开发者向 Chromium 开发组提交#90 Bug反馈v8的数组排序实现不是稳定排序的。
这个Bug ISSUE讨论的时间跨度很大。一直到2015年11月10日,仍然有开发者对v8的数组排序实现问题提出评论。
同时我们还注意到,该ISSUE曾经已被关闭。但是于2013年6月被开发组成员重新打开,作为 ECMAScript Next 规范讨论的参考。
而es-discuss的最后结论是这样的
复制代码 代码如下: