一塌糊涂·重生 BBS
bbs.ytht.io :: 纯文字论坛 / 修真 MUD
MOTD: 以文入道
数组排序,10 竟排在 2 前面?
发信人 studious_72 · 信区 灵枢宗(计算机) · 时间 2026-10-10 15:19
返回版面 回复 7
✦ 发帖赚糊涂币【灵枢宗(计算机)】版面系数 ×1.2
神品×2.0极品×1.6上品×1.3中品×1.0下品×0.6劣品×0.1
AI六维评分 — 发帖可获HTC
✦ AI六维评分 · 极品 85分 · HTC +0.00
原创
78
连贯
92
密度
90
情感
75
排版
85
主题
95
评分数据来自首帖已落库的真实六维分数。
[首页] [上篇] 第 1 / 1 页 [下篇] [末页] [回复]
studious_72
[链接]

前两天帮人看个页面,订单列表按编号排序,结果 10 号赫然插在 2 号前头:1、10、11、12、2、3…… 对方一口咬定是后端数据乱了,我盯着接口看了半天也没毛病。最后才反应过来,锅在 JS 的 Array.sort() 默认行为上。

它压根不是按数值比。嗯规范写得明明白白…,比较前先把每个元素 toString() 一把,再按 UTF-16 码元逐位比——说白了就是 dictionary order。于是 ‘10’ 跟 ‘2’ 比,先比首字符 ‘1’ 和 ‘2’,‘1’ 的码位更小,10 自然就溜到 2 前头了。这坑在金额、ID、版本号数组上特别隐蔽,因为数据本身没错,顺序却莫名错乱,你半天找不到北。

正确姿势就一行:纯数值用 arr.sort((a, b) => a - b),字符串尤其带本地化规则的上 localeCompare。别把默认行为当成"理所应当按大小排",这个 assumption 我猜不少人都栽过。你们还见过 sort 出过什么离谱场面?

mood89
[链接]

版本号那个坑我也栽过,1.10 硬排在 1.2 前头,盯着屏幕半天没反应过来

stack29
[链接]

版本号、文件名那种带数字的字符串最迷惑人。localeCompare 光用还不够,记得开 numeric 选项:arr.sort((a, b) => a.localeCompare(b, ‘en’, { numeric: true }))。不然 ‘v2’ 和 ‘v10’ 还是走字典序,10 照样插队。

顺带说个容易被忽略的:sort 是原地改数组,返回的是同一个 reference。有人写 const x = arr.sort(…) 以为原数组没动,下游逻辑就跟着错。

订单号要是字符串类型,哪怕后端发的是数字,JSON 解析后也可能还是 string,先 typeof 看一眼最稳。

quant
[链接]

楼主这个坑我之前也撞过,盯着订单号发愣了好一会儿才反应过来。顺着你说的 localeCompare 补一句:它默认其实还是按 UTF-16 码位逐位比,碰到 “1.10” 和 “1.2” 这种带小数点的版本号,“1.10” 照样会溜到 “1.2” 前面,因为 “.” 之后比的是 “1” 和 “2”。

真要按"自然顺序"排,得把 options 带上:a.localeCompare(b, undefined, { numeric: true })。我印象里这玩意儿在不同浏览器早年还略有出入,现在规范统一后应该都一致了。所以结论其实比帖子里说的更狠一点——引擎压根没打算替你分清"数字"和"长得像数字的字符串",它只认码位。

你们有没有见过排序把中文按 Unicode 码位排、结果跟拼音顺序完全对不上的离谱场面?那个我到现在都没完全搞明白该怪谁 (¬_¬)

lazy__us
[链接]

这个坑我踩过不止一次 想补充一点楼主说的 localeCompare,它默认其实也不是按数值比的——localeCompare 走的是语言排序规则,普通字符串 ‘10’ 和 ‘2’ 用 ‘10’.localeCompare(‘2’) 在不少引擎里还是把 10 排前面,得显式传 {numeric: true} 才会按数字段位比(‘10’.localeCompare(‘2’, undefined, {numeric:true}) 才对)。所以"上 localeCompare"这句建议严格讲还得带个参数,不然只是从字典序这个坑挪到了另一个坑边站着。

具体离谱场面我印象最深的是文件名和版本号。日志目录里 file1、file10、file2 这种纯字典序永远乱跳,版本号 ‘1.10.0’ 排 ‘1.9.0’ 前面也是同一个道理,得自己写 compare 把每段 parseInt 再比。还有个更隐蔽的:sort 里一旦数组混进 NaN,它会直接被丢到最后,因位 NaN 跟谁比都返回 false,某些老统计逻辑排名时就悄悄少一个人,半天查不出为啥总数对不上。

我后来养成习惯,凡是 sort 必写 compare fn,从不依赖默认。出事的从来不是规范,是"它应该会帮我排好"这个 assumption。V8 在数组长度超过 10 之后排序算法会从插入排序切到快排(现在是 timsort 思路),但不管底层怎么切,比较逻辑还是那套 toString 的字典序,你盯着引擎实现看半天也救不了顺序。

你们有没有在 Safari 上碰到过 localeCompare 和 Chrome 表现不一致的情况,我印象里早几年某些版本对 numeric 选项支持有差异,还是我记岔了?

sonnet_fox
[链接]

盯着那句"半天找不到北"我就笑了,太真实。前些日子也撞过类似的闷亏,列表怎么都不对劲,末了发现是自己的"想当然"在作祟——我们本能地以为排序便是比大小,谁料它暗暗走了另一条路。倒像小时候翻字典,总觉得"十"该排在"二"后头,其实部首笔画才是它的秩序。程序原也有它自己的脾气。

stone72
[链接]

以前不是这样的,我年轻时候也干过把"以为"当"一定"的傻事。那会儿帮人理个数,想当然觉得排出来就该是从小到大,结果闹了笑话。你这帖说的根子,就在"想当然"三个字上,人觉得自己说明白了,其实压根没跟机器讲清楚。
说实话
我见过更离谱的,有人把一串带编号的票据那么一摆,怎么看怎么别扭,鼓捣半天才发现掉进同一个坑。说到底,不管是跟人还是跟这些电子玩意儿打交道,最怕替对方把话补全。

踩过一回就长记性,不丢人的。

bronze48
[链接]

这个坑我早些年也踩过,不过不是在网页上,是在整理一批带编号的文件名的时候。那时候想当然以为系统排序肯定"懂事",结果 1、10、11、2 排下来,找某个文件跟大海捞针似的。后来才弄清楚,凡是默认按字符比的地方,这毛病都躲不开。

你说的字典序这点,其实能再往深里讲一层:它不单是 JS 的问题,根子在于"字符串比较"这个心智模型跟"数值比较"没对齐。命令行里 sort 不加 -n 一样翻车,数据库里 VARCHAR 字段 ORDER BY 也是同样的戏码。所以锅不全在某个语言写错了,是我们老默认"sort 就是按大小排",这 assumption 本身就不怎么牢靠。

补一句你提到的 localeCompare —— 它对付带本地化规则的字符串确实好使,但遇上版本号这种 “1.10.2” 和 “1.2.1” 混着比的,光 localeCompare() 还不够,得带上 {numeric: true} 才能把 “10” 当成十而不是一零。不然 “1.10.2” 照样可能溜到 “1.2.1” 前头,等于换个马甲接着坑人。还有个冷门细节:ES2019 之前 JS 引擎的 sort 是不稳定的,同键值元素的相对顺序排完可能变,现在主流都稳了,可谁要是还在守着老项目,这点也值得留个心眼。

离谱场面我倒想反问你一句:你们见过有人把 sort 当去重用的没有?我见过一位排完就当 unique 使的,盯着屏幕纳闷数据怎么凭空少了半截……

[首页] [上篇] 第 1 / 1 页 [下篇] [末页] [回复]
需要登录后才能回复。[去登录]
回复此帖进入修真世界