算法中有许多排序算法,它们有的是稳定的,有的是不稳定的。最近,碰到了一个由 Chrome V8 不稳定排序算法引发的 Bug。
先说结论,Chrome 70 以下版本的 Array.prototype.sort,对于长度大于 10 个的数组采用的是快速排序,它是一种不稳定的排序算法。在 Chrome 70 之后,采用了稳定的TimSort算法。
事故代码
出现问题的是一个有以下基本逻辑的 React Hooks 组件:
- 有一个降序排列的数组,从第一项开始按序轮播,间隔 5 秒。
- useEffect 中监听数组,数组改变后自动高亮到第一个选项,因为认为这是新的数据了。
- render 方法中再次进行了排序。
- 可自由点击其他条目,高亮点击项。
由于平时开发都使用版本较新的 Chorme,开发过程中一直没发现这个组件有问题。偶然间在一个 64 版本的 Chrome 浏览器上测试时,出现了严重的 Bug:
- 点击其他条目,总是瞬间跳回并选中第一个。
- 每隔五秒,轮播到下一项到瞬间,跳回并选中第一个。
- 出现上述现象的过程中,伴随这某些数值相等到条目随机跳动。
事故原因
经过排查,发现问题的主要原因是排序算法的不稳定性。不稳定的排序,造成了相同的数组,在每次排序后,值相等的几个条目之间,顺序不一致,触发了 effect 逻辑。因此,在 sort 采用的是不稳定排序算法的浏览器中,都会出现上述问题。
还有一个问题是,在 render 方法中,执行了排序操作。它本意是为了保险,再次对数组进行了排序。因为排序不稳定,每次计算出的数组内容都是不同的(相同项前后两次顺序不一致)。这里每次渲染都重新计算,造成了一定不必要的开销,应在上层业务组件中处理一次排序即可。
通过这个网站,可以测试你浏览器的排序是不是稳定的。
排序算法
既然碰到了这个问题,顺便复习一下排序算法。稳定的排序算法有:冒泡排序、归并排序和插入排序等。不稳定的则有选择排序、快速排序、希尔排序、堆排序。
冒泡排序
1 | const bubbleSort = arr => { |