JS 入门通关手册(40):数组高频面试题:去重、扁平化、排序(最全方案 + 性能对比)

张开发
2026/4/7 10:53:47 15 分钟阅读

分享文章

JS 入门通关手册(40):数组高频面试题:去重、扁平化、排序(最全方案 + 性能对比)
摘要本文聚焦前端面试与业务开发中最核心的三大数组操作 —— 数组去重、数组扁平化、数组排序整合 15 种高频实现方案从 “基础 API 快捷写法” 到 “手写底层原理”从 “日常业务最优解” 到 “面试手写满分版”附带详细性能对比、适用场景拆解与面试避坑指南代码可直接复制运行兼顾实用性与考点覆盖帮你一站式吃透数组高频考点轻松应对笔试与面试。一、前言为什么这三类题是面试必考数组是 JavaScript 中最常用的数据结构而去重、扁平化、排序是数组操作中 “出场率最高” 的三类题 —— 既考察基础 API 运用能力也考察手写代码、逻辑思维和性能优化意识无论是初级还是中级前端面试几乎都会考笔试题高频。本文不做冗余讲解只给 “能直接用、能直接背、能直接答面试” 的内容每一种方案都标注优缺点、性能等级和适用场景避免你踩坑。二、数组去重8 种方案按 “实用性 面试优先级” 排序核心需求将数组中重复的元素删除返回无重复的新数组分 “基本类型去重” 和 “对象数组去重”面试常考后者。方案 1Set 去重日常业务首选最简写法javascript运行// 基础版基本类型去重 const unique (arr) [...new Set(arr)]; // 实战版兼容空值、undefined、Symbol const unique (arr) Array.from(new Set(arr)); // 示例 const arr [1, 2, 2, 3, undefined, undefined, null, null, Symbol(a), Symbol(a)]; console.log(unique(arr)); // [1, 2, 3, undefined, null, Symbol(a)]✅ 优点代码极简、可读性高、性能优秀时间复杂度 O (n)、兼容 ES6❌ 缺点无法去重引用类型对象、数组等因为 Set 按 “引用地址” 判断唯一性 适用场景日常业务中基本类型数组number、string、null 等去重方案 2Map 去重面试高频支持对象数组去重javascript运行// 基础版基本类型 const unique (arr) { const map new Map(); return arr.filter(item !map.has(item) map.set(item, true)); }; // 面试重点对象数组去重按指定key去重如id const uniqueByKey (arr, key) { const map new Map(); // 保留第一个出现的元素过滤后续重复元素 return arr.filter(item !map.has(item[key]) map.set(item[key], item)); }; // 示例对象数组去重 const userArr [ { id: 1, name: 张三 }, { id: 2, name: 李四 }, { id: 1, name: 张三 } // 重复 ]; console.log(uniqueByKey(userArr, id)); // 保留id为1、2的两个对象✅ 优点支持所有类型、可控制对象去重规则、性能优秀O (n)❌ 缺点代码比 Set 略繁琐 适用场景面试手写、对象数组去重高频考点方案 3indexOf/includes 去重面试手写基础版javascript运行// indexOf 版 const unique (arr) { const res []; arr.forEach(item { // 若res中没有当前元素才pushindexOf返回-1表示无 if (res.indexOf(item) -1) { res.push(item); } }); return res; }; // includes 版更简洁推荐 const unique (arr) { const res []; arr.forEach(item !res.includes(item) res.push(item)); return res; };✅ 优点兼容低版本浏览器、逻辑简单面试手写易实现❌ 缺点性能一般时间复杂度 O (n²)forEach indexOf 双重循环 适用场景面试手写基础题、兼容低版本项目方案 4双层 for 循环 splice面试手写保底版javascript运行const unique (arr) { // 浅拷贝避免修改原数组面试加分点 let newArr [...arr]; const len newArr.length; // 外层循环控制当前元素 for (let i 0; i len; i) { // 内层循环对比后续元素 for (let j i 1; j len; j) { if (newArr[i] newArr[j]) { newArr.splice(j, 1); // 删除重复元素 len--; // 数组长度减少避免漏判 j--; // 指针回退重新判断当前位置 } } } return newArr; };✅ 优点无 API 依赖、面试手写易想到、兼容所有浏览器❌ 缺点性能最差O (n²)、会修改临时数组需注意浅拷贝面试避坑点 适用场景面试手写保底实在想不出其他方法时用方案 5对象键名去重兼容旧环境面试拓展javascript运行const unique (arr) { const obj {}; const res []; arr.forEach(item { // 利用对象键名唯一避免重复注意处理数字/字符串类型统一 if (!obj[item]) { obj[item] true; res.push(item); } }); return res; };✅ 优点兼容极低版本浏览器、性能中等O (n)❌ 缺点会将数字转为字符串如 1 和 1 会被认为重复、不支持 Symbol 适用场景旧项目兼容、面试拓展回答体现思维全面性补充去重方案性能对比面试必答表格方案时间复杂度性能等级适用场景Set 去重O(n)⭐⭐⭐⭐⭐日常基本类型去重Map 去重O(n)⭐⭐⭐⭐⭐面试、对象数组去重indexOf/includesO(n²)⭐⭐⭐基础手写、低版本兼容双层 for 循环O(n²)⭐⭐手写保底对象键名O(n)⭐⭐⭐⭐旧项目兼容面试避坑点不要直接用 splice 修改原数组面试扣分点需先浅拷贝Set/Map 去重时对象会按引用地址判断无法去重内容相同的不同对象数字和字符串类型如 1 和 1只有对象键名去重会误判其他方案正常。三、数组扁平化5 种方案从简到难面试手写重点核心需求将多维数组如 [1, [2, [3]]]转为一维数组[1, 2, 3]面试常考 “不使用 flat 方法” 手写实现。方案 1flat (Infinity)日常业务首选最简写法javascript运行// 基础版无限层级扁平化 const flatten (arr) arr.flat(Infinity); // 进阶版指定层级扁平化如2层 const flatten (arr, depth 1) arr.flat(depth); // 示例 const arr [1, [2, [3, [4]]]]; console.log(flatten(arr)); // [1, 2, 3, 4] console.log(flatten(arr, 2)); // [1, 2, 3, [4]]✅ 优点代码极简、无需手写、支持指定层级❌ 缺点依赖 ES6、面试不允许直接用需手写底层 适用场景日常业务开发、无需兼容旧环境方案 2递归实现面试手写基础版javascript运行const flatten (arr) { const res []; // 遍历数组判断每个元素是否为数组 arr.forEach(item { // 若是数组递归调用拼接结果 if (Array.isArray(item)) { res.push(...flatten(item)); } else { // 若不是数组直接push res.push(item); } }); return res; };✅ 优点逻辑清晰、面试易手写、无 API 依赖❌ 缺点层级极深时如 10000 层会栈溢出面试拓展点 适用场景面试手写基础题、普通层级数组方案 3reduce 递归面试手写优化版javascript运行// 极简版面试加分代码简洁 const flatten (arr) { return arr.reduce((pre, cur) { // 若当前元素是数组递归扁平化后拼接否则直接添加 return pre.concat(Array.isArray(cur) ? flatten(cur) : cur); }, []); // 初始值为空数组 };✅ 优点代码简洁、体现 reduce 运用能力面试加分、逻辑清晰❌ 缺点同样存在栈溢出问题 适用场景面试手写优化版、展示代码简洁度方案 4栈实现非递归面试高频解决栈溢出javascript运行const flatten (arr) { const stack [...arr]; // 浅拷贝避免修改原数组 const res []; // 栈不为空循环执行 while (stack.length 0) { const item stack.pop(); // 从栈尾取出元素 if (Array.isArray(item)) { stack.push(...item); // 若是数组拆解后压入栈尾 } else { res.unshift(item); // 若不是数组插入结果数组头部 } } return res; };✅ 优点无递归、无栈溢出、性能优秀O (n)、面试高频❌ 缺点代码比递归略繁琐 适用场景面试手写高分版、深层级数组避免栈溢出方案 5字符串转换法简洁但有坑面试避坑javascript运行const flatten (arr) { // 转为字符串拆分后转为数字仅适用于纯数字数组 return arr.toString().split(,).map(Number); };✅ 优点代码极简、无需递归❌ 缺点仅适用于纯数字数组、会改变元素类型如 null 转为 0、不支持复杂类型 适用场景纯数字数组快速扁平化日常应急面试不推荐面试避坑点递归实现会栈溢出面试时需提及 “可用栈实现非递归版本”加分点flat (Infinity) 虽简洁但面试必问 “不使用 flat 如何实现”字符串转换法有类型限制面试时需指出其缺点体现思维严谨。四、数组排序7 种方案分 “业务常用” 和 “面试手写”核心需求对数组进行升序 / 降序排序分 “基本类型排序” 和 “对象数组排序”面试重点考察 “手写排序算法”。一、业务常用排序无需手写直接用方案 1sort 基础排序日常首选javascript运行// 数字数组升序核心a - b const sortAsc (arr) [...arr].sort((a, b) a - b); // 数字数组降序核心b - a const sortDesc (arr) [...arr].sort((a, b) b - a); // 示例 const arr [3, 1, 4, 1, 5, 9]; console.log(sortAsc(arr)); // [1, 1, 3, 4, 5, 9] console.log(sortDesc(arr)); // [9, 5, 4, 3, 1, 1]✅ 优点代码极简、日常够用、性能优秀❌ 缺点sort 底层是不稳定排序V8 引擎短数组插入排序长数组快速排序 适用场景日常业务、无需关注排序稳定性方案 2对象数组按字段排序面试高频业务题javascript运行// 按指定字段升序 const sortByKeyAsc (arr, key) { return [...arr].sort((a, b) a[key] - b[key]); }; // 按指定字段降序 const sortByKeyDesc (arr, key) { return [...arr].sort((a, b) b[key] - a[key]); }; // 示例按age排序 const userArr [ { name: 张三, age: 25 }, { name: 李四, age: 20 }, { name: 王五, age: 30 } ]; console.log(sortByKeyAsc(userArr, age)); // 按age升序✅ 优点适配业务常用场景、代码简洁❌ 缺点仅适用于数字类型字段 适用场景对象数组排序如表格排序、列表排序二、面试手写排序算法必背 3 种高频考点方案 3冒泡排序最基础必背javascript运行// 升序排序手写标准版 const bubbleSort (arr) { let newArr [...arr]; // 浅拷贝不修改原数组面试加分 const len newArr.length; // 外层循环控制排序轮次n个元素需n-1轮 for (let i 0; i len - 1; i) { let flag false; // 优化标记是否有交换无交换则提前退出 // 内层循环对比相邻元素交换位置 for (let j 0; j len - i - 1; j) { if (newArr[j] newArr[j 1]) { // 交换两个元素解构赋值简洁 [newArr[j], newArr[j 1]] [newArr[j 1], newArr[j]]; flag true; } } if (!flag) break; // 无交换说明已排序完成提前退出 } return newArr; };✅ 优点逻辑最简单、易手写、稳定排序❌ 缺点性能较差O (n²) 面试考点优化点flag 标记、不修改原数组、稳定排序的定义方案 4选择排序手写基础易理解javascript运行const selectSort (arr) { let newArr [...arr]; const len newArr.length; // 外层循环控制当前要排序的位置 for (let i 0; i len; i) { let minIndex i; // 假设当前位置是最小值索引 // 内层循环找到后续最小值的索引 for (let j i 1; j len; j) { if (newArr[j] newArr[minIndex]) { minIndex j; // 更新最小值索引 } } // 交换当前位置和最小值位置的元素 [newArr[i], newArr[minIndex]] [newArr[minIndex], newArr[i]]; } return newArr; };✅ 优点逻辑清晰、易手写、交换次数少❌ 缺点性能较差O (n²)、不稳定排序 面试考点与冒泡排序的区别交换次数、稳定性方案 5插入排序性能优于冒泡 / 选择面试加分javascript运行const insertSort (arr) { let newArr [...arr]; const len newArr.length; // 外层循环从第二个元素开始第一个元素默认有序 for (let i 1; i len; i) { let temp newArr[i]; // 保存当前要插入的元素 let j i - 1; // 有序区域的最后一个索引 // 内层循环找到插入位置 while (j 0 newArr[j] temp) { newArr[j 1] newArr[j]; // 元素后移 j--; } newArr[j 1] temp; // 插入元素 } return newArr; };✅ 优点性能优于冒泡 / 选择O (n²)但实际执行更快、稳定排序❌ 缺点大数据量时性能一般 面试考点V8 引擎中短数组长度≤22会用插入排序加分点补充排序算法对比面试必答表格排序算法时间复杂度稳定性适用场景冒泡排序O(n²)稳定面试手写基础选择排序O(n²)不稳定面试手写、少量数据插入排序O(n²)稳定短数组、日常少量数据sort 排序短数组 O (n²)、长数组 O (nlogn)不稳定日常业务首选面试避坑点手写排序时必须浅拷贝数组避免修改原数组扣分点冒泡排序的优化点flag 标记的必须写体现性能意识回答 “sort 底层原理” 时要说明 “V8 引擎短数组插入排序长数组快速排序”。五、高频面试题标准答案直接背问数组去重有哪些方法Set 去重的缺点是什么答常用方法有 Set、Map、indexOf、双层循环、对象键名Set 无法去重引用类型对象、数组因为按引用地址判断唯一性。问手写数组扁平化不使用 flat 方法答优先写递归版简洁补充栈实现版解决栈溢出加分并说明两种方案的优缺点。问手写冒泡排序并说明如何优化答写出带 flag 标记的冒泡排序提前退出无交换的轮次说明优化点是 “减少无效循环提升性能”。问如何对对象数组去重 / 排序答去重用 Map 按指定 key 过滤排序用 sort 结合 key 对比a [key] - b [key]。问Set 和 Map 去重的区别答Set 更简洁适合基本类型Map 可控制去重规则适合对象数组去重可保留原对象结构。六、总结核心要点面试速记数组去重日常用 Set面试写 Map对象数组保底写 indexOf / 双层循环数组扁平化日常用 flat (Infinity)面试写递归基础 栈实现优化数组排序日常用 sort面试手写冒泡必背、插入加分记住 sort 底层原理所有手写题务必浅拷贝数组避免修改原数组面试加分关键重点掌握 “对象数组去重 / 排序”是业务与面试的高频交叉点。

更多文章