先记住这个答案
普通数组 sort 没有比较函数时,会把参与比较的非 undefined 元素转成字符串,再按 UTF-16 码元序列比较。字符串 "10" 的首位比 "9" 小,所以十可以排在九前面。已确认是普通有限 Number 的数组做升序时,可提供 (a, b) => a - b,比较器返回值的负、零、正分别表示前后或相等关系。sort 会修改原数组,默认字符串顺序也不是人类语言排序。混合类型、NaN、对象或特殊业务值应先明确排序键和无效值策略,不能无条件套减法。
- 默认比较按字符串而非数字大小
- 比较器看返回符号,不是布尔真假
- sort 修改原数组,排序键与缺失值需明确
相同输入只替换比较函数
下面分别在副本上做默认排序和数值升序,让源数组保持不变。二十的字符串首位也会影响默认结果,说明这不是十和九的特殊例外,而是比较键发生了变化。
示例使用已知有限数字,减法可以清楚表达数值大小关系。复制只是为了隔离 sort 的原地副作用,不代表排序会深拷贝对象,也不能作为所有数据都适用该比较器的证据。
const values = [10, 9, 20, 2];
console.log(JSON.stringify([...values].sort()));
console.log(JSON.stringify([...values].sort((a, b) => a - b)));
console.log(JSON.stringify(values));查看输出与解释
[10,2,20,9]
[2,9,10,20]
[10,9,20,2]默认比较把十、二、二十、九按字符串序列排列,数值比较器得到正常升序。源数组未变来自先创建副本,若直接 values.sort 则会修改它。
比较器必须表达一致的顺序关系
返回 a > b 只有 true 和 false,转换后缺少负数方向,无法完整表达一项应排在另一项之前。比较器应尽量纯粹,对同一对输入给出一致结果,并遵守合理的反对称与传递关系。
若元素是业务记录,应先选择 price、createdAt 等明确键,并处理相同键的次级规则。不要在比较回调中修改记录或依赖不断变化的外部状态,否则算法调用顺序会把结果变得难以解释。
缺失值、NaN 和本地化需要专门策略
减法遇到 NaN 可能产生无法表达预期顺序的返回值,混合字符串或空值也可能发生意外转换。应先校验输入,或明确无效值排前、排后、拒绝排序等策略,再编写比较器。
用户姓名、带数字的产品名或不同语言文本,可能需要 Intl.Collator 等本地化比较能力。排序稳定只表示比较相等时保留原相对顺序,不会把默认字符串比较自动修正为业务想要的数值或语言规则。
容易答错的地方
- 认为稳定排序就能保证数字大小正确
- 稳定性只约束比较器认为相等的元素之间的相对次序,不改变默认比较规则。数值升序仍需明确数值比较器,并验证缺失或无效数据的处理,不能用一个算法性质替代业务排序定义。
- 比较器返回布尔值就觉得足够
- 布尔值只能提供零和一,不能完整表示负方向,容易违反顺序关系要求。应返回能区分前、后和相等的数值,并使用相反输入顺序及相等键测试检查一致性。
面试官还会怎么问?
对象数组直接 sort 会按某个字段吗?
不会自动知道业务字段,默认转换得到的字符串通常也不适合业务顺序。应显式提取排序键并定义缺失值与相同键策略,避免依赖对象默认字符串表示。
sort 与 toSorted 如何选择?
sort 修改当前数组,toSorted 在支持它的环境里提供复制后排序的接口。二者都需要正确比较规则,是否复制容器与怎样比较元素是独立选择,同时应检查目标运行环境支持。
数字字符串能直接用减法比较吗?
可能触发数值转换,但应先确认字符串确实满足业务数值格式,空字符串、非法文本和精度范围都需要处理。显式解析与校验能让失败更清楚,不能把隐式转换当作完整输入验证。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。