如何在JavaScript中正确实现数组排序?

来源:MAC教程作者:马来西亚程序员头衔:程序员
导读:本期聚焦于马来西亚程序员创作的《如何在JavaScript中正确实现数组排序?》,敬请观看详情。[1,2,10].sort() 返回 [1,10,2],这个结果常让刚接触 JavaScript 的开发者感到困惑。原因在于 Array.prototype.sort 默认将元素转换为字符串并按 UTF-16 码元顺序比较,数字 10 的字符串形式排在 2 前面。要得到数值升序,必须传入比较函数 (a,b)=a-b。本文围绕 JavaScript 数组排序的实现机制展开,介绍比较函数返回值与排序稳定性、数字和字符串排序差异、对象数组按字段排序、中文 localeCompare 以及性能注意事项,并给出可直接运行的代码示例。理解这些细节后,可以避免排序结果不符合业务预期的问题。

在 JavaScript 中调用数组的 sort 方法看似简单,实际上默认排序规则经常和直觉相悖。例如对一个纯数字数组执行 [1, 2, 10].sort(),得到的结果是 [1, 10, 2],而不是预期的 [1, 2, 10]。这并非引擎缺陷,而是由 ECMAScript 规范定义的默认行为:当不传入比较函数时,元素会先被转换为字符串,再按照字符串的 UTF-16 码元值从小到大排列。因此数字 10 的字符串形式 "10" 与 "2" 比较时,先比较第一个字符 "1" 和 "2",由于字符 "1" 的码元值小于 "2",所以 10 排在 2 前面。这个机制也解释了为什么 [1, 2, 10].sort() 会出现看似错误的结果。

如何在JavaScript中正确实现数组排序?

要控制排序顺序,需要向 sort 方法传入一个比较函数。比较函数接收两个参数,通常记作 a 和 b,如果返回值小于 0,a 会排在 b 之前;如果返回值大于 0,a 会排在 b 之后;如果返回值等于 0,两个元素的相对位置通常保持不变(但严格稳定需依赖引擎与规范版本)。这个约定看起来简单,却是很多排序需求的基础。

一、sort 方法的默认行为与比较函数机制

如果不传比较函数,sort 并非按数值大小排序,而是调用每个元素的 toString 方法,将元素转换为字符串后按码元顺序比较。对于字符串数组,这种默认行为有时是可接受的;但一旦混杂数字、null、undefined 或对象,结果就可能变得难以预测。例如 [3, 1, 11].sort() 同样会得到 [1, 11, 3],因为字符串 "11" 小于 "3"。

比较函数的核心在于返回值的正负,而不是布尔判断。一个常见的错误是写成 (a, b) => a > b,这会把布尔值转换成 1 或 0,导致无法表达完整的排序关系。正确做法是返回数值,例如升序使用 a - b,降序使用 b - a。对于字符串,则可以使用 localeCompare 或按条件返回 -1、1、0。

const nums = [1, 2, 10];
console.log(nums.sort()); // [1, 10, 2]

// 升序
nums.sort((a, b) => a - b);
console.log(nums); // [1, 2, 10]

// 降序
nums.sort((a, b) => b - a);
console.log(nums); // [10, 2, 1]

从上面代码可以看出,同一个数组连续调用 sort 会修改原数组并返回该数组的引用,因此打印 nums 时已经发生变化。如果需要保留原始顺序,应该先复制数组再排序。

二、数字、字符串和对象数组的排序实践

数字排序中最常用的是减法比较器,但要注意浮点数相减可能产生极小的误差,导致比较结果不稳定。例如 0.1 + 0.2 的结果并不是精确的 0.3,在大量浮点数排序时,推荐使用 a - b 已经足够应对一般场景;如果对精度要求较高,可以先缩放为整数,或在比较函数中做条件判断。

字符串排序如果使用默认方式,会按 UTF-16 码元值排序,造成所有大写字母排在小写字母之前,例如 "Cherry" 会排在 "apple" 前面。对于需要忽略大小写或按本地化规则排序的场景,应使用 localeCompare。中文排序也可以借助 localeCompare 并传入 zh-CN 区域参数,按拼音顺序排列。

const fruits = ['banana', 'apple', 'Cherry'];
fruits.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
console.log(fruits); // ['apple', 'banana', 'Cherry']

const cities = ['北京', '上海', '广州', '深圳'];
cities.sort((a, b) => a.localeCompare(b, 'zh-CN'));
console.log(cities); // 按拼音排序

对象数组排序通常需要指定某个字段作为依据。可以直接返回两个对象字段的差值,也可以在差值相同时继续比较下一个字段,实现多级排序。下面示例先按库存升序排列,再按价格升序排列,这样库存相同的商品会按照价格从低到高显示。

const items = [
  { name: '苹果', price: 12, stock: 3 },
  { name: '香蕉', price: 6, stock: 9 },
  { name: '橙子', price: 8, stock: 3 }
];

items.sort((a, b) => {
  if (a.stock !== b.stock) {
    return a.stock - b.stock;
  }
  return a.price - b.price;
});
console.log(items.map(i => i.name).join(', ')); // 橙子, 苹果, 香蕉

如果对象中的某个字段可能是空值或字符串,比较前最好做类型转换或空值检查,否则减法可能产生 NaN,导致排序结果不符合预期。实际业务中建议把排序字段先标准化,再进入比较函数。

三、排序稳定性、性能与常见误区

排序稳定性是指当比较函数返回 0 时,相等元素的原始先后顺序是否会被保留。ES2019 起,ECMAScript 规范明确要求 Array.prototype.sort 必须是稳定排序,现代浏览器和 Node.js 的 V8 引擎已经实现这一特性。稳定性在多级排序场景中非常有用,例如先按姓名排序,再按分数排序时,分数相同的人会保持姓名排序后的相对位置。

const students = [
  { name: 'B', score: 80 },
  { name: 'A', score: 90 },
  { name: 'C', score: 80 }
];
students.sort((a, b) => a.score - b.score);
console.log(students.map(i => i.name).join('')); // BAC,稳定排序保留 B 在 C 前

性能方面,sort 方法底层一般使用 TimSort 或快速排序的变体,时间复杂度平均为 O(n log n)。比较函数如果包含复杂计算、DOM 访问或多次类型转换,排序耗时会被显著放大。对于大数据量数组,尽量让比较函数只做简单的数值或字符串比较,可以提前将需要比较的字段映射为简单类型。

常见误区还包括忘记 sort 是原地排序、误用返回布尔值的比较器、以及没有处理数组中的空值。原地排序意味着直接在原数组上修改,如果其他代码仍引用该数组,可能引发意外的数据变化。复制数组可以使用 slice() 或较新的 toSorted 方法。

const original = [3, 1, 2];
const sorted = original.slice().sort((a, b) => a - b);
console.log(original); // [3, 1, 2]
console.log(sorted);   // [1, 2, 3]

// 使用 toSorted 返回新数组(需现代浏览器或 Node 20+)
const sorted2 = original.toSorted((a, b) => a - b);
console.log(sorted2); // [1, 2, 3]

最后再强调一次,比较函数返回布尔值是无效的。布尔值 true 会被强制转换为 1,false 转换为 0,这样比较器只能表示两种状态,无法覆盖负数情况,排序逻辑会变得混乱。只要比较函数返回数值并严格遵守负数、零、正数的约定,JavaScript 数组排序就能保持清晰和可预测。

JavaScript数组排序排序比较函数稳定排序修改时间:2026-09-30 03:32:49

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/0930/63666.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。