0

0

JavaScript 通用排序函数设计与实现:优化重复代码模式

霞舞

霞舞

发布时间:2025-11-07 14:13:01

|

1053人浏览过

|

来源于php中文网

原创

JavaScript 通用排序函数设计与实现:优化重复代码模式

本文探讨了如何在javascript中通过设计一个通用排序函数来优化重复的排序逻辑。针对不同属性(如字符串、数字)的数组元素排序场景,文章介绍了一种结合`map`和`sort`的高阶函数方法,有效减少代码冗余,提高可维护性,并提供了详细的实现步骤和示例代码,帮助开发者构建灵活高效的排序解决方案。

1. 重复排序逻辑的挑战

在JavaScript开发中,尤其是在处理包含复杂对象的数组时,根据对象的不同属性进行排序是一个非常常见的需求。例如,在一个待办事项列表中,用户可能希望按标题、截止日期或优先级进行排序。初学者往往会为每个排序字段编写一个独立的排序函数,导致代码中出现大量相似的、重复的逻辑。

考虑以下针对待办事项数组的排序函数示例:

// 原始的按标题排序函数
function sortTitle(array) {
  return array.sort((a, b) => {
    const titleA = a.title.toUpperCase();
    const titleB = b.title.toUpperCase();
    return (titleA > titleB) - (titleA < titleB); // 简化比较
  });
}

// 原始的按日期排序函数
function sortDate(array) {
  return array.sort((a, b) => {
    return (a.duedate > b.duedate) - (a.duedate < b.duedate); // 简化比较
  });
}

// 原始的按优先级排序函数
function sortPrio(array) {
  return array.sort((a, b) => {
    return (a.prio > b.prio) - (a.prio < b.prio); // 简化比较
  });
}

这些函数的核心逻辑都是调用 array.sort() 并提供一个比较器函数,其差异仅在于访问对象属性的方式(a.title、a.duedate、a.prio)以及对字符串进行大小写转换的处理。这种重复的代码模式不仅增加了代码量,降低了可读性,也使得后期维护(例如,修改排序逻辑或添加新字段)变得繁琐且容易出错。

2. 设计通用的高阶排序函数

为了解决上述问题,我们可以设计一个通用的高阶排序函数。这个函数将接受两个参数:待排序的数组和一个“键提取函数”(key extractor function)。“键提取函数”的作用是从数组中的每个复杂对象中提取出用于比较的标量值(例如,字符串、数字),从而将排序的“策略”与排序的“机制”分离。

立即学习Java免费学习笔记(深入)”;

这种设计思想与计算机科学中的“Schwartzian transform”技术有异曲同工之妙,其核心步骤如下:

  1. 映射 (Map):遍历原始数组,对每个元素应用“键提取函数”,生成一个包含 [键值, 原始元素] 对的中间数组。
  2. 排序 (Sort):根据这些对中的“键值”对中间数组进行排序。
  3. 还原 (Map):从排序后的中间数组中提取出原始元素,形成最终的排序结果。

3. 通用 sortBy 函数的实现

下面是实现通用 sortBy 函数的代码:

/**
 * 通用排序函数,根据指定的键提取函数对数组进行排序。
 * 该函数返回一个新数组,不修改原数组。
 *
 * @param {Array} array 待排序的数组。
 * @param {Function} keyFn 一个函数,接受数组中的一个元素,返回用于排序的键值。
 *                         例如:item => item.title.toUpperCase() 或 item => item.duedate。
 * @returns {Array} 排序后的新数组。
 */
const sortBy = (array, keyFn) => array
    // 步骤1: 映射 - 提取键值,创建 [键值, 原始元素] 对
    .map(item => [keyFn(item), item])
    // 步骤2: 排序 - 根据键值对对进行排序
    .sort((a, b) => {
        const keyA = a[0]; // 第一个元素的键值
        const keyB = b[0]; // 第二个元素的键值
        // 简洁的比较函数:
        // 当 keyA > keyB 返回 1
        // 当 keyA < keyB 返回 -1
        // 当 keyA === keyB 返回 0
        return (keyA > keyB) - (keyA < keyB);
    })
    // 步骤3: 还原 - 提取排序后的原始元素
    .map(pair => pair[1]);

代码解析:

  • array.map(item => [keyFn(item), item]): 这一步遍历 array 中的每个 item。对于每个 item,它调用 keyFn(item) 来获取排序依据的键值,然后创建一个新的数组 [keyFn(item), item]。这个新数组的第一个元素是键值,第二个元素是原始对象。
  • .sort((a, b) => (keyA > keyB) - (keyA : 这一步对 map 生成的中间数组进行排序。a 和 b 分别是 [键值, 原始元素] 形式的对。比较器函数通过 a[0] 和 b[0] 访问键值进行比较。
    • keyA > keyB:如果 keyA 大于 keyB,结果为 true (转换为 1)。
    • keyA
    • 当 keyA > keyB 时,表达式为 1 - 0 = 1。
    • 当 keyA
    • 当 keyA === keyB 时,表达式为 0 - 0 = 0。
    • 这种写法是 Array.prototype.sort() 比较器函数的一种简洁实现,适用于数字和字符串的升序排序。
  • .map(pair => pair[1]): 排序完成后,我们只需要原始的待办事项对象,因此再次使用 map 遍历排序后的对,并只返回每个对中的第二个元素(即原始对象)。

4. 实际应用示例

假设我们有以下待办事项数据:

let todos = [
    { title: 'Buy Milk', duedate: 8, prio: 2 },
    { title: 'Learn JavaScript', duedate: 9, prio: 1 },
    { title: 'Write Report', duedate: 7, prio: 3 },
    { title: 'Read Book', duedate: 6, prio: 1 },
    { title: 'Attend Meeting', duedate: 10, prio: 2 },
];

现在,我们可以使用 sortBy 函数轻松地按不同属性进行排序:

成新网络商城购物系统
成新网络商城购物系统

使用模板与程序分离的方式构建,依靠专门设计的数据库操作类实现数据库存取,具有专有错误处理模块,通过 Email 实时报告数据库错误,除具有满足购物需要的全部功能外,成新商城购物系统还对购物系统体系做了丰富的扩展,全新设计的搜索功能,自定义成新商城购物系统代码功能代码已经全面优化,杜绝SQL注入漏洞前台测试用户名:admin密码:admin888后台管理员名:admin密码:admin888

下载

按标题排序 (字符串,忽略大小写)

// 注意:对于字符串排序,如果需要忽略大小写,应在 keyFn 中进行转换
const sortedByTitle = sortBy(todos, item => item.title.toUpperCase());
console.log("按标题排序:", sortedByTitle);
/* 输出示例 (顺序可能因实际数据而异):
[
  { title: 'Attend Meeting', duedate: 10, prio: 2 },
  { title: 'Buy Milk', duedate: 8, prio: 2 },
  { title: 'Learn JavaScript', duedate: 9, prio: 1 },
  { title: 'Read Book', duedate: 6, prio: 1 },
  { title: 'Write Report', duedate: 7, prio: 3 }
]
*/

按截止日期排序 (数字)

const sortedByDate = sortBy(todos, item => item.duedate);
console.log("按日期排序:", sortedByDate);
/* 输出示例:
[
  { title: 'Read Book', duedate: 6, prio: 1 },
  { title: 'Write Report', duedate: 7, prio: 3 },
  { title: 'Buy Milk', duedate: 8, prio: 2 },
  { title: 'Learn JavaScript', duedate: 9, prio: 1 },
  { title: 'Attend Meeting', duedate: 10, prio: 2 }
]
*/

按优先级排序 (数字)

const sortedByPrio = sortBy(todos, item => item.prio);
console.log("按优先级排序:", sortedByPrio);
/* 输出示例:
[
  { title: 'Learn JavaScript', duedate: 9, prio: 1 },
  { title: 'Read Book', duedate: 6, prio: 1 },
  { title: 'Buy Milk', duedate: 8, prio: 2 },
  { title: 'Attend Meeting', duedate: 10, prio: 2 },
  { title: 'Write Report', duedate: 7, prio: 3 }
]
*/

集成到事件监听器

在实际应用中,当用户点击不同的排序按钮时,可以这样使用 sortBy 函数:

// 假设 titleSort, dateSort, prioSort 是 DOM 元素
// cleanTodo() 和 showTodos() 是更新UI的函数

titleSort.addEventListener("click", () => {
    // sortBy 返回一个新数组,因此需要重新赋值给 todos 或处理新数组
    todos = sortBy(todos, item => item.title.toUpperCase());
    cleanTodo(); // 清理旧的UI显示
    showTodos(); // 渲染排序后的待办事项
});

dateSort.addEventListener("click", () => {
    todos = sortBy(todos, item => item.duedate);
    cleanTodo();
    showTodos();
});

prioSort.addEventListener("click", () => {
    todos = sortBy(todos, item => item.prio);
    cleanTodo();
    showTodos();
});

5. 优势与注意事项

优势:

  • 代码优化与可维护性: 显著减少了代码冗余,将多种排序逻辑抽象为一个通用函数,使得代码更简洁、易于理解和维护。
  • 灵活性与可扩展性: 开发者可以轻松地添加新的排序字段,只需提供一个新的 keyFn 即可,无需修改 sortBy 函数本身或编写新的排序函数。
  • 纯函数特性: sortBy 函数返回一个新数组,不修改原始数组,这符合函数式编程的原则,有助于避免副作用,提高代码的预测性。

注意事项:

  • 性能考量: sortBy 函数内部使用了两次 map 和一次 sort。对于非常大的数据集(例如,数万甚至数十万条数据),创建中间数组可能会带来一定的内存开销和计算时间。然而,在大多数Web应用场景下,这种性能影响通常可以忽略不计。
  • 复杂排序逻辑: 对于需要多级排序(例如,先按优先级升序,再按日期降序)或更复杂的比较规则,keyFn 可能需要返回一个复合键(例如,一个包含多个排序字段的数组),或者 sort 比较器本身需要进行更精细的定制。
  • 数据类型兼容性: 确保 keyFn 返回的值是可比较的类型(如数字、字符串)。如果属性可能为 null 或 undefined,keyFn 或比较器需要额外处理这些边界情况,以避免意外行为。

6. 总结

通过设计和实现一个通用的 sortBy 高阶函数,我们成功地将重复的排序逻辑抽象化,大大提升了代码的复用性和可维护性。这种模式不仅适用于排序,也体现了JavaScript中高阶函数在实现灵活、可扩展和优雅代码方面的强大能力。在面对相似且重复的代码模式时,开发者应积极思考如何利用抽象和参数化来构建更通用、更健壮的解决方案。

相关专题

更多
js获取数组长度的方法
js获取数组长度的方法

在js中,可以利用array对象的length属性来获取数组长度,该属性可设置或返回数组中元素的数目,只需要使用“array.length”语句即可返回表示数组对象的元素个数的数值,也就是长度值。php中文网还提供JavaScript数组的相关下载、相关课程等内容,供大家免费下载使用。

536

2023.06.20

js刷新当前页面
js刷新当前页面

js刷新当前页面的方法:1、reload方法,该方法强迫浏览器刷新当前页面,语法为“location.reload([bForceGet]) ”;2、replace方法,该方法通过指定URL替换当前缓存在历史里(客户端)的项目,因此当使用replace方法之后,不能通过“前进”和“后退”来访问已经被替换的URL,语法为“location.replace(URL) ”。php中文网为大家带来了js刷新当前页面的相关知识、以及相关文章等内容

372

2023.07.04

js四舍五入
js四舍五入

js四舍五入的方法:1、tofixed方法,可把 Number 四舍五入为指定小数位数的数字;2、round() 方法,可把一个数字舍入为最接近的整数。php中文网为大家带来了js四舍五入的相关知识、以及相关文章等内容

706

2023.07.04

js删除节点的方法
js删除节点的方法

js删除节点的方法有:1、removeChild()方法,用于从父节点中移除指定的子节点,它需要两个参数,第一个参数是要删除的子节点,第二个参数是父节点;2、parentNode.removeChild()方法,可以直接通过父节点调用来删除子节点;3、remove()方法,可以直接删除节点,而无需指定父节点;4、innerHTML属性,用于删除节点的内容。

470

2023.09.01

JavaScript转义字符
JavaScript转义字符

JavaScript中的转义字符是反斜杠和引号,可以在字符串中表示特殊字符或改变字符的含义。本专题为大家提供转义字符相关的文章、下载、课程内容,供大家免费下载体验。

388

2023.09.04

js生成随机数的方法
js生成随机数的方法

js生成随机数的方法有:1、使用random函数生成0-1之间的随机数;2、使用random函数和特定范围来生成随机整数;3、使用random函数和round函数生成0-99之间的随机整数;4、使用random函数和其他函数生成更复杂的随机数;5、使用random函数和其他函数生成范围内的随机小数;6、使用random函数和其他函数生成范围内的随机整数或小数。

989

2023.09.04

如何启用JavaScript
如何启用JavaScript

JavaScript启用方法有内联脚本、内部脚本、外部脚本和异步加载。详细介绍:1、内联脚本是将JavaScript代码直接嵌入到HTML标签中;2、内部脚本是将JavaScript代码放置在HTML文件的`<script>`标签中;3、外部脚本是将JavaScript代码放置在一个独立的文件;4、外部脚本是将JavaScript代码放置在一个独立的文件。

652

2023.09.12

Js中Symbol类详解
Js中Symbol类详解

javascript中的Symbol数据类型是一种基本数据类型,用于表示独一无二的值。Symbol的特点:1、独一无二,每个Symbol值都是唯一的,不会与其他任何值相等;2、不可变性,Symbol值一旦创建,就不能修改或者重新赋值;3、隐藏性,Symbol值不会被隐式转换为其他类型;4、无法枚举,Symbol值作为对象的属性名时,默认是不可枚举的。

537

2023.09.20

苹果官网入口直接访问
苹果官网入口直接访问

苹果官网直接访问入口是https://www.apple.com/cn/,该页面具备0.8秒首屏渲染、HTTP/3与Brotli加速、WebP+AVIF双格式图片、免登录浏览全参数等特性。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

10

2025.12.24

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 2.9万人学习

TypeScript 教程
TypeScript 教程

共19课时 | 1.7万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 2.6万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号