JavaScript 常用数组方法备忘

79 阅读8分钟

摘要:本文梳理了JavaScript数组操作方法,涵盖增删改查、遍历转换、排序去重等功能。通过代码示例、使用场景、缺陷分析及纵向延伸问答,巩固对JavaScrpt数组的认知及日常使用备忘。


1. push() 与 pop()

概述

  • push():在数组末尾添加元素,返回新长度。
  • pop():删除并返回最后一个元素。
  • 直接修改原数组

代码示例

let arr = [1, 2, 3];
arr.push(4); // arr: [1, 2, 3, 4]
arr.pop();   // 返回 4,arr: [1, 2, 3]

使用场景

  • 实现栈(先进后出)逻辑,如撤销/重做功能。
  • 动态追加数据(如消息队列末尾)。

缺陷分析

  • push() 可能导致内存溢出(大数组慎用)。
  • pop() 删除后无法恢复,需手动备份。

纵向延伸问答

Q:如何实现类似 push() 的不可变操作?
A:使用扩展运算符 arr = [...arr, 4],生成新数组。


2. unshift() 与 shift()

概述

  • unshift():在数组开头添加元素,返回新长度。
  • shift():删除并返回第一个元素。
  • 直接修改原数组

代码示例

let arr = [1, 2, 3];
arr.unshift(0); // arr: [0, 1, 2, 3]
arr.shift();    // 返回 0,arr: [1, 2, 3]

使用场景

  • 实现队列(先进先出)逻辑,如任务调度。
  • 优先级插入(如高优先级任务前置)。

缺陷分析

  • unshift()/shift() 性能较差(需重排索引)。
  • 大数组频繁操作会导致性能瓶颈。

纵向延伸问答

Q:如何高效实现队列的头部插入?
A:使用双端队列(Deque)或链表结构替代数组。


3. splice()

概述

  • 从任意位置删除/添加元素,返回被删除的元素数组。
  • 直接修改原数组

代码示例

let arr = [1, 2, 3, 4];
let removed = arr.splice(1, 2, 'a', 'b'); 
// arr: [1, "a", "b", 4], removed: [2, 3]

使用场景

  • 灵活编辑数组中间内容(如表格数据更新)。
  • 批量删除无效数据(如过滤特定索引范围)。

缺陷分析

  • 参数复杂(需明确起始索引、删除数量)。
  • 大规模操作可能导致性能问题。

纵向延伸问答

Q:如何用 splice() 实现数组去重?
A:结合 indexOf() 判断重复项,例如:

for (let i = 0; i < arr.length; i++) {
  if (arr.indexOf(arr[i]) < i) arr.splice(i, 1);
}

4. slice()

概述

  • 返回指定区间的浅拷贝子数组,不修改原数组

代码示例

let arr = [1, 2, 3, 4];
let sub = arr.slice(1, 3); // [2, 3]

使用场景

  • 分页数据截取(如 slice(0, 10))。
  • 浅拷贝数组(slice() 无参数时)。

缺陷分析

  • 浅拷贝问题(嵌套数组或对象需深拷贝)。
  • 额外内存开销(生成新数组)。

纵向延伸问答

Q:如何实现深拷贝?
A:结合 JSON.parse(JSON.stringify(arr)) 或递归复制。


5. map()

概述

  • 对每个元素执行回调,返回新数组。
  • 不修改原数组

代码示例

let nums = [1, 2, 3];
let squares = nums.map(x => x * x); // [1, 4, 9]

使用场景

  • 数据转换(如单位换算)。
  • 生成派生数组(如 HTML 元素列表)。

缺陷分析

  • 无副作用(仅适合纯函数场景)。
  • 大数组可能占用较多内存。

纵向延伸问答

Q:map()forEach() 的区别?
A:map() 返回新数组,forEach() 无返回值;map() 适合映射,forEach() 适合副作用操作。


6. filter()

概述

  • 过滤满足条件的元素,返回新数组。
  • 不修改原数组

代码示例

let nums = [1, 2, 3, 4];
let even = nums.filter(x => x % 2 === 0); // [2, 4]

使用场景

  • 条件筛选(如搜索过滤)。
  • 移除无效数据(如空值清理)。

缺陷分析

  • 无法处理 NaNNaN !== NaN)。
  • 大数组可能占用较多内存。

纵向延伸问答

Q:如何过滤唯一值?
A:结合 Set[...new Set(arr)]


7. reduce()

概述

  • 累积器处理数组,返回单一值(如总和)。
  • 不修改原数组

代码示例

let sum = [1, 2, 3].reduce((acc, curr) => acc + curr, 0); // 6

使用场景

  • 聚合计算(如求和、平均值)。
  • 数据扁平化(嵌套数组合并)。

缺陷分析

  • 初值处理需谨慎(未提供初值时以第一个元素为初值)。
  • 回调逻辑复杂时易出错。

纵向延伸问答

Q:如何用 reduce() 实现数组分组?
A:

let grouped = arr.reduce((acc, item) => {
  let key = item.category;
  acc[key] = acc[key] || [];
  acc[key].push(item);
  return acc;
}, {});

8. sort()

概述

  • 对数组排序(默认按字符串 Unicode 排序)。
  • 直接修改原数组

代码示例

let nums = [3, 1, 4];
nums.sort(); // [1, 3, 4]
nums.sort((a, b) => b - a); // [4, 3, 1]

使用场景

  • 数据排序(如排行榜)。
  • 自定义排序规则(如对象属性排序)。

缺陷分析

  • 默认排序逻辑可能不符合预期(需自定义比较函数)。
  • 修改原数组(需先复制)。

纵向延伸问答

Q:如何稳定排序?
A:在比较函数中加入索引判断,确保相同值的顺序不变。


9. includes()

概述

  • 判断数组是否包含某元素,返回布尔值。

代码示例

let arr = [1, 2, 3];
console.log(arr.includes(2)); // true

使用场景

  • 快速判断元素存在性(如权限校验)。
  • 替代 indexOf() 的更直观写法。

缺陷分析

  • 无法处理 NaNNaN !== NaN)。
  • 无参数时默认从索引0开始查找。

纵向延伸问答

Q:如何判断 NaN 是否存在?
A:使用 Number.isNaN()Object.is()


10. Array.from()

概述

  • 从类数组对象或可迭代对象创建数组。

代码示例

let arr = Array.from('hello'); // ['h', 'e', 'l', 'l', 'o']

使用场景

  • 转换类数组对象(如 argumentsNodeList)。
  • 生成固定长度数组(如 [...Array(5)])。

缺陷分析

  • 非可迭代对象会抛异常(如 nullundefined)。

纵向延伸问答

Q:如何用 Array.from() 生成指定范围的数字数组?
A:

Array.from({length: 5}, (_, i) => i + 1); // [1, 2, 3, 4, 5]

11. flat() 与 flatMap()

概述

  • flat():展开嵌套数组。
  • flatMap():先映射再展开。

代码示例

let arr = [1, [2, [3]]];
let flat = arr.flat(2); // [1, 2, 3]
let flatMap = arr.flatMap(x => x); // [1, 2, [3]]

使用场景

  • 嵌套数据扁平化(如多维数组处理)。
  • 映射后展开(如字符串拆分后去空)。

缺陷分析

  • flat() 默认展开深度为1,需指定参数。
  • flatMap() 可能返回嵌套数组(需二次展开)。

12. find() 与 findIndex()

概述

  • find():返回满足条件的第一个元素。
  • findIndex():返回满足条件的第一个元素的索引。

代码示例

let users = [{id:1, name:'Alice'}, {id:2, name:'Bob'}];
let user = users.find(u => u.id === 2); // {id:2, name:'Bob'}
let index = users.findIndex(u => u.id === 1); // 0

使用场景

  • 查询特定元素(如用户 ID 搜索)。
  • 快速定位索引(如删除指定元素)。

缺陷分析

  • 仅返回第一个匹配项(需遍历全部时需额外处理)。
  • 未找到时返回 undefined 或 -1,需判断。

13. some() 与 every()

概述

  • some():至少一个元素满足条件返回 true
  • every():所有元素满足条件返回 true

代码示例

let nums = [1, 2, 3];
console.log(nums.some(x => x > 2)); // true
console.log(nums.every(x => x > 0)); // true

使用场景

  • 条件验证(如表单必填字段检查)。
  • 数据完整性校验(如所有用户状态为激活)。

缺陷分析

  • 无法获取具体满足条件的元素。
  • 大数组遍历可能影响性能。

14. fill() 与 copyWithin()

概述

  • fill():填充数组指定区间。
  • copyWithin():复制数组内部元素到指定位置。

代码示例

let arr = [1, 2, 3, 4];
arr.fill(0, 1, 3); // [1, 0, 0, 4]
arr.copyWithin(2, 0, 2); // [1, 0, 1, 0]

使用场景

  • 初始化数组(如 [0,0,0])。
  • 快速复制数组片段(如数据滑动)。

缺陷分析

  • fill() 会覆盖原数组,不可逆。
  • copyWithin() 参数易混淆(目标索引、起始索引、结束索引)。

汇总总结

方法是否修改原数组主要用途注意事项
push()添加元素到末尾性能问题(大数组慎用)
pop()删除末尾元素无法恢复删除值
unshift()添加元素到开头性能问题(大数组慎用)
shift()删除开头元素无法恢复删除值
splice()灵活增删改参数复杂
slice()提取子数组浅拷贝问题
map()数据映射生成新数组
filter()条件筛选生成新数组
reduce()累积计算初值处理需谨慎
sort()自定义排序默认排序逻辑需注意
includes()快速判断元素是否存在无法处理 NaN
Array.from()转换类数组对象依赖可迭代性
flat()展开嵌套数组默认展开深度为1
find()查询特定元素仅返回第一个匹配项

关键建议

  1. 修改原数组 vs 生成新数组

    • 若需保留原数组,优先使用 slice()map() 等非修改方法。
    • 若需高效操作,可直接使用 push()splice() 等修改方法。
  2. 性能优化

    • 避免在 unshift()/shift() 中处理大数组(性能较差)。
    • reduce() 和 map() 在链式调用时需注意内存占用。
  3. 兼容性

    • includes()flat() 等 ES6 方法需注意旧环境兼容性(如 IE 不支持)。
  4. 深度理解

    • 掌握方法底层原理(如 reduce() 的累积器逻辑)。
    • 结合实际场景选择最优解(如 filter() 过滤 vs splice() 删除)。

通过系统掌握数组方法及其特性,开发者可高效处理数据操作需求,同时避免常见错误,提升代码质量与性能。