JavaScript 中 Array.prototype.sort 的稳定性与比较函数陷阱
最近在重构一个数据表格组件时,遇到一个看似简单却折腾了半天的 bug:明明按状态和时间排序,结果分页后部分行顺序乱跳。排查后发现根源是 `Array.prototype.sort` 的默认行为与比较函数返回值处理。在 V8 引擎中,数组长度小于等于 10 时使用插入排序,大于 10 时使用快速排序,而快速排序在旧版 V8 中是不稳定的。虽然 ES2019 已经要求 `sort` 必须稳定,但 Node.js 10 及之前、部分旧浏览器仍可能不稳定,并且比较函数返回非数字、返回布尔值、返回浮点数都会导致不可预期的排序结果。这篇文章会从实际代码出发,分析排序不稳定的表现、比较函数的正确写法、数字与字符串排序差异,以及如何用 `Intl.Collator` 处理中文排序。 ## 问题复现:分页后顺序抖动 假设有一个订单列表,按状态(pending、paid、shipped)排序,相同状态内按创建时间倒序。最初代码写得很随意: ```js orders.sort((a, b) => { if (a.status === b.status) { return a.createdAt > b.createdAt; // 返回布尔值,错误 } return a.status > b.status; // 同样返回布尔值 }); ``` 比较函数返回布尔值时,V8 会强制转成 0 或 1,但不会返回负数。这意味着 `a` 小于 `b` 时返回 0,会被当成相等;`a` 大于 `b` 时返回 1,表示 a 排在 b 后面。一旦两个元素比较返回 0,相对顺序就依赖引擎稳定性。在旧版 Node 或某些安卓 WebView 里,快速排序的 partition 过程会把相等元素打乱,导致每次分页请求返回的列表顺序不一致。 修正方式很简单:比较函数必须返回负数、0、正数。 ```js orders.sort((a, b) => { if (a.status !== b.status) { return a.status { const statusDiff = a.status.localeCompare(b.status); return statusDiff !== 0 ? statusDiff : b.createdAt - a.createdAt; }); ``` ## 数字排序的经典陷阱 `[10, 1, 3, 20].sort()` 返回 `[1, 10, 20, 3]`,因为默认把元素转成字符串再按 UTF-16 码元比较。数字排序必须传入比较函数: ```js const nums = [10, 1, 3, 20]; nums.sort((a, b) => a - b); // [1, 3, 10, 20] ``` 注意 `a - b` 可能产生 `NaN`,如果数组里有 `undefined`、`null`、字符串数字混合,结果会不可控。稳妥的做法是先过滤或统一类型: ```js const clean = nums.filter(n => typeof n === 'number' && !Number.isNaN(n)); clean.sort((a, b) => a - b); ``` ## 中文字符串排序不能依赖 localeCompare 默认参数 `localeCompare` 不带参数时使用运行时默认 locale,在 Node.js 和浏览器、不同操作系统上可能不同。中文按拼音排序应该显式指定: ```js const names = ['张三', '李四', '王五', '阿一']; names.sort((a, b) => a.localeCompare(b, 'zh-Hans-CN', { sensitivity: 'base' })); // 阿一, 李四, 王五, 张三 ``` `sensitivity: 'base'` 让大小写和音调不参与比较。如果要做表格列排序,通常还需要处理 `null`、空字符串,把它们固定放在末尾: ```js function compareNullable(a, b, compareFn) { if (a == null && b == null) return 0; if (a == null) return 1; if (b == null) return -1; return compareFn(a, b); } ``` ## 稳定性在业务代码中的实际影响 假设从后端拿到已经按时间倒序的订单,前端只按状态做一次稳定排序,那么相同状态的订单应该保持时间倒序。如果排序不稳定,这个隐含顺序就会丢失。ES2019 之后所有现代浏览器和 Node.js 11+ 都保证 `sort` 稳定,但如果你维护的项目需要兼容旧环境,不要依赖默认稳定性。一个常见的兜底方案是使用索引作为次级比较键: ```js const withIndex = orders.map((item, index) => ({ item, index })); withIndex.sort((a, b) => { const diff = a.item.status.localeCompare(b.item.status); return diff !== 0 ? diff : a.index - b.index; }); const sorted = withIndex.map(entry => entry.item); ``` 这样即使引擎不稳定,相同状态的相对顺序也由原始索引决定,不会乱跳。 ## 性能上的注意点 比较函数会被调用多次,复杂度大约是 O(n log n)。在比较函数里做复杂计算、正则匹配、DOM 查询都会拖慢排序。如果排序键需要多次读取,提前算好缓存起来: ```js const decorated = largeList.map(item => ({ item, sortKey: computeExpensiveKey(item), })); decorated.sort((a, b) => a.sortKey - b.sortKey); const result = decorated.map(d => d.item); ``` 另外,`sort` 会原地修改数组。如果原数组还要用,先 `slice()` 复制一份。React 状态里直接 `sort` 原数组还会引发意外的引用相等问题,导致组件不更新或更新错乱。 ```js // 错误:直接修改 state items.sort(compareFn); setItems(items); // 正确:返回新数组 setItems([...items].sort(compareFn)); ``` 把这些细节处理完,表格排序和分页顺序就稳定了,也不再需要靠刷新页面来碰运气。 推荐查看天气:http://www.362tq.com/danzhou/10tian.html 推荐查看天气:http://www.362tq.com/danzhou/15tian.html 推荐查看天气:http://www.362tq.com/danzhou/40tian.html 推荐查看天气:http://www.362tq.com/wenchang/ 推荐查看天气:http://www.362tq.com/wenchang/zuotian.html 推荐查看天气:http://www.362tq.com/wenchang/7tian.html 推荐查看天气:http://www.362tq.com/wenchang/10tian.html 推荐查看天气:http://www.362tq.com/wenchang/15tian.html 推荐查看天气:http://www.362tq.com/wenchang/40tian.html 推荐查看天气:http://www.362tq.com/wanning/ 推荐查看天气:http://www.362tq.com/wanning/zuotian.html 推荐查看天气:http://www.362tq.com/wanning/7tian.html 推荐查看天气:http://www.362tq.com/wanning/10tian.html 推荐查看天气:http://www.362tq.com/wanning/15tian.html 推荐查看天气:http://www.362tq.com/wanning/40tian.html 推荐查看天气:http://www.362tq.com/dongfang/ 推荐查看天气:http://www.362tq.com/dongfang/zuotian.html 推荐查看天气:http://www.362tq.com/dongfang/7tian.html 推荐查看天气:http://www.362tq.com/dongfang/10tian.html 推荐查看天气:http://www.362tq.com/dongfang/15tian.html 推荐查看天气:http://www.362tq.com/dongfang/40tian.html 推荐查看天气:http://www.362tq.com/dingan/ 推荐查看天气:http://www.362tq.com/dingan/zuotian.html 推荐查看天气:http://www.362tq.com/dingan/7tian.html 推荐查看天气:http://www.362tq.com/dingan/10tian.html 推荐查看天气:http://www.362tq.com/dingan/15tian.html 推荐查看天气:http://www.362tq.com/dingan/40tian.html 推荐查看天气:http://www.362tq.com/tunchang/ 推荐查看天气:http://www.362tq.com/tunchang/zuotian.html 推荐查看天气:http://www.362tq.com/tunchang/7tian.html 推荐查看天气:http://www.362tq.com/tunchang/10tian.html 推荐查看天气:http://www.362tq.com/tunchang/15tian.html 推荐查看天气:http://www.362tq.com/tunchang/40tian.html 推荐查看天气:http://www.362tq.com/chengmai/ 推荐查看天气:http://www.362tq.com/chengmai/zuotian.html 推荐查看天气:http://www.362tq.com/chengmai/7tian.html 推荐查看天气:http://www.362tq.com/chengmai/10tian.html 推荐查看天气:http://www.362tq.com/chengmai/15tian.html 推荐查看天气:http://www.362tq.com/chengmai/40tian.html 推荐查看天气:http://www.362tq.com/lingao/ 推荐查看天气:http://www.362tq.com/lingao/zuotian.html 推荐查看天气:http://www.362tq.com/lingao/7tian.html 推荐查看天气:http://www.362tq.com/lingao/10tian.html 推荐查看天气:http://www.362tq.com/lingao/15tian.html 推荐查看天气:http://www.362tq.com/lingao/40tian.html 推荐查看天气:http://www.362tq.com/baisha/ 推荐查看天气:http://www.362tq.com/baisha/zuotian.html 推荐查看天气:http://www.362tq.com/baisha/7tian.html 推荐查看天气:http://www.362tq.com/baisha/10tian.html 推荐查看天气:http://www.362tq.com/baisha/15tian.html 推荐查看天气:http://www.362tq.com/baisha/40tian.html 推荐查看天气:http://www.362tq.com/changjiang/ 推荐查看天气:http://www.362tq.com/changjiang/zuotian.html 推荐查看天气:http://www.362tq.com/changjiang/7tian.html 推荐查看天气:http://www.362tq.com/changjiang/10tian.html 推荐查看天气:http://www.362tq.com/changjiang/15tian.html 推荐查看天气:http://www.362tq.com/changjiang/40tian.html 推荐查看天气:http://www.362tq.com/ledong/ 推荐查看天气:http://www.362tq.com/ledong/zuotian.html 推荐查看天气:http://www.362tq.com/ledong/7tian.html 推荐查看天气:http://www.362tq.com/ledong/10tian.html 推荐查看天气:http://www.362tq.com/ledong/15tian.html 推荐查看天气:http://www.362tq.com/ledong/40tian.html 推荐查看天气:http://www.362tq.com/lingshui/ 推荐查看天气:http://www.362tq.com/lingshui/zuotian.html 推荐查看天气:http://www.362tq.com/lingshui/7tian.html 推荐查看天气:http://www.362tq.com/lingshui/10tian.html 推荐查看天气:http://www.362tq.com/lingshui/15tian.html 推荐查看天气:http://www.362tq.com/lingshui/40tian.html 推荐查看天气:http://www.362tq.com/baoting/ 推荐查看天气:http://www.362tq.com/baoting/zuotian.html 推荐查看天气:http://www.362tq.com/baoting/7tian.html 推荐查看天气:http://www.362tq.com/baoting/10tian.html 推荐查看天气:http://www.362tq.com/baoting/15tian.html 推荐查看天气:http://www.362tq.com/baoting/40tian.html 推荐查看天气:http://www.362tq.com/qiongzhong/ 推荐查看天气:http://www.362tq.com/qiongzhong/zuotian.html 推荐查看天气:http://www.362tq.com/qiongzhong/7tian.html 推荐查看天气:http://www.362tq.com/qiongzhong/10tian.html 推荐查看天气:http://www.362tq.com/qiongzhong/15tian.html 推荐查看天气:http://www.362tq.com/qiongzhong/40tian.html 推荐查看天气:http://www.362tq.com/xishaqundao/ 推荐查看天气:http://www.362tq.com/xishaqundao/zuotian.html 推荐查看天气:http://www.362tq.com/xishaqundao/7tian.html 推荐查看天气:http://www.362tq.com/xishaqundao/10tian.html 推荐查看天气:http://www.362tq.com/xishaqundao/15tian.html 推荐查看天气:http://www.362tq.com/xishaqundao/40tian.html 推荐查看天气:http://www.362tq.com/nansha/ 推荐查看天气:http://www.362tq.com/nansha/zuotian.html 推荐查看天气:http://www.362tq.com/nansha/7tian.html 推荐查看天气:http://www.362tq.com/nansha/10tian.html 推荐查看天气:http://www.362tq.com/nansha/15tian.html 推荐查看天气:http://www.362tq.com/nansha/40tian.html 推荐查看天气:http://www.362tq.com/zhongshaqundao/ 推荐查看天气:http://www.362tq.com/zhongshaqundao/zuotian.html 推荐查看天气:http://www.362tq.com/zhongshaqundao/7tian.html 推荐查看天气:http://www.362tq.com/zhongshaqundao/10tian.html 推荐查看天气:http://www.362tq.com/zhongshaqundao/15tian.html 推荐查看天气:http://www.362tq.com/zhongshaqundao/40tian.html 推荐查看天气:http://www.362tq.com/xiantao/ 推荐查看天气:http://www.362tq.com/xiantao/zuotian.html 推荐查看天气:http://www.362tq.com/xiantao/7tian.html 推荐查看天气:http://www.362tq.com/xiantao/10tian.html 推荐查看天气:http://www.362tq.com/xiantao/15tian.html 推荐查看天气:http://www.362tq.com/xiantao/40tian.html 推荐查看天气:http://www.362tq.com/qianjiang/ 推荐查看天气:http://www.362tq.com/qianjiang/zuotian.html 推荐查看天气:http://www.362tq.com/qianjiang/7tian.html 推荐查看天气:http://www.362tq.com/qianjiang/10tian.html 推荐查看天气:http://www.362tq.com/qianjiang/15tian.html 推荐查看天气:http://www.362tq.com/qianjiang/40tian.html 推荐查看天气:http://www.362tq.com/tianmen/ 推荐查看天气:http://www.362tq.com/tianmen/zuotian.html 推荐查看天气:http://www.362tq.com/tianmen/7tian.html 推荐查看天气:http://www.362tq.com/tianmen/10tian.html 推荐查看天气:http://www.362tq.com/tianmen/15tian.html 推荐查看天气:http://www.362tq.com/tianmen/40tian.html 推荐查看天气:http://www.362tq.com/shennongjia/ 推荐查看天气:http://www.362tq.com/shennongjia/zuotian.html 推荐查看天气:http://www.362tq.com/shennongjia/7tian.html 推荐查看天气:http://www.362tq.com/shennongjia/10tian.html 推荐查看天气:http://www.362tq.com/shennongjia/15tian.html 推荐查看天气:http://www.362tq.com/shennongjia/40tian.html 推荐查看天气:http://www.362tq.com/yancheng/ 推荐查看天气:http://www.362tq.com/yancheng/zuotian.html 推荐查看天气:http://www.362tq.com/yancheng/7tian.html 推荐查看天气:http://www.362tq.com/yancheng/10tian.html 推荐查看天气:http://www.362tq.com/yancheng/15tian.html 推荐查看天气:http://www.362tq.com/yancheng/40tian.html 推荐查看天气:http://www.362tq.com/xiangshui/ 推荐查看天气:http://www.362tq.com/xiangshui/zuotian.html 推荐查看天气:http://www.362tq.com/xiangshui/7tian.html 推荐查看天气:http://www.362tq.com/xiangshui/10tian.html 推荐查看天气:http://www.362tq.com/xiangshui/15tian.html 推荐查看天气:http://www.362tq.com/xiangshui/40tian.html 推荐查看天气:http://www.362tq.com/binhai/ 推荐查看天气:http://www.362tq.com/binhai/zuotian.html 推荐查看天气:http://www.362tq.com/binhai/7tian.html 推荐查看天气:http://www.362tq.com/binhai/10tian.html 推荐查看天气:http://www.362tq.com/binhai/15tian.html 推荐查看天气:http://www.362tq.com/binhai/40tian.html 推荐查看天气:http://www.362tq.com/funing/ 推荐查看天气:http://www.362tq.com/funing/zuotian.html 推荐查看天气:http://www.362tq.com/funing/7tian.html 推荐查看天气:http://www.362tq.com/funing/10tian.html 推荐查看天气:http://www.362tq.com/funing/15tian.html 推荐查看天气:http://www.362tq.com/funing/40tian.html 推荐查看天气:http://www.362tq.com/sheyang/ 推荐查看天气:http://www.362tq.com/sheyang/zuotian.html 推荐查看天气:http://www.362tq.com/sheyang/7tian.html 推荐查看天气:http://www.362tq.com/sheyang/10tian.html 推荐查看天气:http://www.362tq.com/sheyang/15tian.html 推荐查看天气:http://www.362tq.com/sheyang/40tian.html 推荐查看天气:http://www.362tq.com/jianhu/ 推荐查看天气:http://www.362tq.com/jianhu/zuotian.html 推荐查看天气:http://www.362tq.com/jianhu/7tian.html 推荐查看天气:http://www.362tq.com/jianhu/10tian.html 推荐查看天气:http://www.362tq.com/jianhu/15tian.html 推荐查看天气:http://www.362tq.com/jianhu/40tian.html 推荐查看天气:http://www.362tq.com/dongtai/ 推荐查看天气:http://www.362tq.com/dongtai/zuotian.html 推荐查看天气:http://www.362tq.com/dongtai/7tian.html 推荐查看天气:http://www.362tq.com/dongtai/10tian.html 推荐查看天气:http://www.362tq.com/dongtai/15tian.html 推荐查看天气:http://www.362tq.com/dongtai/40tian.html 推荐查看天气:http://www.362tq.com/dafeng/ 推荐查看天气:http://www.362tq.com/dafeng/zuotian.html 推荐查看天气:http://www.362tq.com/dafeng/7tian.html 推荐查看天气:http://www.362tq.com/dafeng/10tian.html 推荐查看天气:http://www.362tq.com/dafeng/15tian.html 推荐查看天气:http://www.362tq.com/dafeng/40tian.html 推荐查看天气:http://www.362tq.com/jincheng/ 推荐查看天气:http://www.362tq.com/jincheng/zuotian.html 推荐查看天气:http://www.362tq.com/jincheng/7tian.html 推荐查看天气:http://www.362tq.com/jincheng/10tian.html 推荐查看天气:http://www.362tq.com/jincheng/15tian.html 推荐查看天气:http://www.362tq.com/jincheng/40tian.html 推荐查看天气:http://www.362tq.com/qinshui/ 推荐查看天气:http://www.362tq.com/qinshui/zuotian.html 推荐查看天气:http://www.362tq.com/qinshui/7tian.html 推荐查看天气:http://www.362tq.com/qinshui/10tian.html 推荐查看天气:http://www.362tq.com/qinshui/15tian.html 推荐查看天气:http://www.362tq.com/qinshui/40tian.html 推荐查看天气:http://www.362tq.com/yangcheng/ 推荐查看天气:http://www.362tq.com/yangcheng/zuotian.html 推荐查看天气:http://www.362tq.com/yangcheng/7tian.html 推荐查看天气:http://www.362tq.com/yangcheng/10tian.html 推荐查看天气:http://www.362tq.com/yangcheng/15tian.html 推荐查看天气:http://www.362tq.com/yangcheng/40tian.html 推荐查看天气:http://www.362tq.com/lingchuan/ 推荐查看天气:http://www.362tq.com/lingchuan/zuotian.html 推荐查看天气:http://www.362tq.com/lingchuan/7tian.html 推荐查看天气:http://www.362tq.com/lingchuan/10tian.html 推荐查看天气:http://www.362tq.com/lingchuan/15tian.html 推荐查看天气:http://www.362tq.com/lingchuan/40tian.html 推荐查看天气:http://www.362tq.com/zezhou/ 推荐查看天气:http://www.362tq.com/zezhou/zuotian.html 推荐查看天气:http://www.362tq.com/zezhou/7tian.html 推荐查看天气:http://www.362tq.com/zezhou/10tian.html 推荐查看天气:http://www.362tq.com/zezhou/15tian.html