本文系统整理了JavaScript数组方法,按"增删改查判"分类总结36个核心方法及其参数、返回值。


重点介绍了sort()、flatMap()等方法的特性和使用场景,对比了破坏性方法与非破坏性方法的差异。


同时指出已废弃或不推荐使用的数组模式,并推荐ES2023新增的toReversed()等非破坏性方法。


通过示例详细解析了flatMap()的数据处理优势,强调其在现代开发中的重要性。


 本文可以看做是下列文章的升级版

JavaScript 数组 array 相关知识整理


关联阅读推荐


JavaScript 常用数组去重方法及性能对比(附:扩展运算符...总结)


JavaScript 字符串和数组方法总结(默写版)ES2023新增:toSorted(), toReversed(), toSpliced(), with()


类数组转数组的方法:Array.from() 和 扩展运算符 的区别(附:支持扩展运算符的类数组对象表)


对比总结:JavaScript Array、Map、weakMap、Set、weakSet


JavaScript 36个数组方法完整参数返回值表

牢记五字真言 “增-删-改-查-判

分类 方法 参数 返回值
concat() ...arrays: any[] 新数组(合并后)
push() ...elements: any[] 新数组长度
unshift() ...elements: any[] 新数组长度
pop() 无参数 被删除的元素
shift() 无参数 被删除的元素
slice() start?: number, end?: number 截取原数组的一部分,返回新数组
splice() start: number, deleteCount?: number, ...items?: any[] 添加/删除,返回被删除的元素组成的数组
(数组) from() arrayLike: any, mapFn?: (element, index) => any 将类数组或可迭代对象转为数组
of() ...elements: any[] 将一组参数转为数组
改元素 copyWithin() target: number, start: number, end?: number 复制部分数组到该数组中其他位置,不改变原数组长度,返回修改后的数组
fill() value: any, start?: number, end?: number 数组填充,返回修改后的数组
扁平 flat() depth?: number 新数组(扁平后)
归并 reduce() callback: (accumulator, current, index, array) => any, initialValue?: any 累加结果
reduceRight() callback: (accumulator, current, index, array) => any, initialValue?: any 累加结果
排序 sort() compareFunction?: (a, b) => number 排序后的原数组(默认升序)
reverse() 无参数 反转后的原数组
转字符串 join() separator?: string string
继承方法 toString() 无参数 返回表示指定数组及其元素的字符串
toLocaleString() 无参数 将数组元素转换为本地化的字符串表示形式。
valueOf() 所有对象都继承自 Object 的方法之一 返回数组本身,而不是数组的字符串表示。
:怎么查(遍历) forEach() callback: (element, index, array) => void undefined
映射 map() callback: (element, index, array) => any 新数组
flatMap() callback: (element, index, array) => any 新数组(扁平后)
迭代 keys() 无参数 Array Iterator
values() 无参数 Array Iterator
entries() 无参数 Array Iterator
过滤 filter() callback: (element, index, array) => boolean 新数组(过滤后)
查什么(位置) indexOf() searchElement: any, fromIndex?: number 索引位置或-1
lastIndexOf() searchElement: any, fromIndex?: number 索引位置或-1
查元素 at() index: number (支持负数) 对应元素或undefined
find() callback: (element, index, array) => boolean 找到的元素或undefined
findIndex() callback: (element, index, array) => boolean 索引位置或-1
isArray() value: any boolean
includes() searchElement: any, fromIndex?: number boolean
some() callback: (element, index, array) => boolean boolean
every() callback: (element, index, array) => boolean boolean

参数类型速记表

参数类型 说明 常用方法
callback函数 (element, index, array) => any mapfilterforEachfindsomeeveryreduce
起始位置 start?: number slicespliceindexOfincludescopyWithinfill
搜索元素 searchElement: any indexOflastIndexOfincludes
分隔符 separator?: string join
深度 depth?: number flat
比较函数 compareFunction?: (a, b) => number sort
初始值 initialValue?: any reducereduceRight

返回值类型分类表

返回值类型 方法 特点
新数组 mapfiltersliceconcatflatflatMapfromof 不改变原数组
原数组 pushpopshiftunshiftsplicesortreversecopyWithinfill 改变原数组
boolean includessomeeveryisArray 判断结果
索引/元素 indexOflastIndexOffindIndexfindat 查找结果
数值 pushunshift 返回长度
字符串 jointoStringtoLocaleString 字符串结果
迭代器 keysvaluesentries 可迭代对象
累加结果 reducereduceRight 归并计算结果
undefined forEach 无返回值

重要参数模式

// 1. 回调函数三件套 (元素, 索引, 数组)
arr.map((element, index, array) => { })

// 2. 起始位置 + 结束位置
arr.slice(start, end)
arr.fill(value, start, end)

// 3. 目标位置 + 源位置  
arr.copyWithin(target, start, end)

JavaScript中已废弃或不推荐使用的数组方法


在 JavaScript 中,数组方法的“废弃”或“不推荐使用”通常分为两类:一类是已被标准标记为废弃的方法,另一类是虽未被废弃但因性能、副作用等问题不推荐使用的模式。同时,随着 ES2023 新方法的引入,一些老方法的使用场景也发生了变化。


📜 JavaScript 中不推荐使用的数组方法及相关模式

方法 / 模式 状态与问题描述 现代替代方案
Array.observe() / Array.unobserve() 已过时 (Obsolete)。它们曾被用于异步观察数组的变化,但已被完全移除,无法使用-8 使用 Proxy 对象来拦截和自定义对数组的操作,实现更灵活和强大的观察机制-8
非标准数组通用方法
(如 Array.slice(myArr, ...)Array.forEach(myArr, ...))
已过时 (Obsolete)。这些方法曾试图将数组实例方法作为静态方法来调用,现已从所有浏览器中移除-8 直接使用标准的实例方法,如 myArr.slice(...)myArr.forEach(...)-8
for...in 循环遍历数组 强烈不推荐for...in 设计用于遍历对象的可枚举属性,而非数组元素。它会遍历出数组原型链上可能存在的其他属性,且遍历顺序不确定,可能导致意外结果-3-6 - 使用 for...of 循环(推荐,可直接获取值)。
- 使用 for 循环(性能好,可获取索引)。
- 使用 forEach() 方法(函数式风格)-6
reverse() 虽未废弃,但需注意副作用。该方法会直接改变原数组。在 React、Vue 等注重状态不可变性的框架中,直接修改原数组容易引发难以追踪的bug-4 使用 ES2023 引入的 toReversed() 方法。它返回一个新数组,原数组保持不变-4
sort() 虽未废弃,但需注意副作用。与 reverse() 类似,sort() 方法也会直接改变原数组,具有破坏性-4 使用 ES2023 引入的 toSorted() 方法。它返回一个排序后的新数组,不影响原数组-4
splice() 虽未废弃,但需注意副作用。用于添加或删除数组中的元素,是直接修改原数组的典型方法-4 使用 ES2023 引入的 toSpliced() 方法。它以非破坏性的方式执行相同的操作,返回修改后的新数组-4
通过索引直接修改元素
(如 arr[index] = value)
虽未废弃,但需注意副作用。这种操作方式同样会直接改变原数组,在不可变数据流中应谨慎使用-4 使用 ES2023 引入的 with() 方法。它返回一个指定索引被替换为新值后的新数组副本-4
过度链式调用
(如 arr.filter().map().slice())
不推荐(从性能角度)。每次调用都会创建并遍历新的中间数组,在处理超大规模数据时,会产生巨大的内存开销和性能损耗-7-9 - 对于性能敏感的场景,考虑使用 for 或 while 循环,将多次操作合并为一次遍历。
- 使用 ES2025/2026 的迭代器助手(如 values().filter().map().take()),它们以懒执行的方式工作,不会创建中间数组,直到最终调用 toArray() 才生成结果-7

💡 核心要点与总结

  1. 区分“废弃”与“不推荐”:像 Array.observe() 这类是真正被标准移除的废弃功能。而 reverse()sort() 这类是标准核心方法,并未废弃,但在现代应用(特别是使用 React、Vue 等框架)中,它们的破坏性(修改原数组) 特性被视为一种不推荐的做法-4

  2. 拥抱非破坏性方法:ES2023 引入的 toReversed()toSorted()toSpliced()with() 是数组操作的未来趋势。它们让代码更可预测,尤其在状态管理中能有效避免副作用,提高代码的健壮性-4

  3. 关注性能与可读性的平衡:对于大多数业务代码,filtermap 等方法足够清晰且性能良好,无需过度优化。但当你在处理成千上万甚至更大数据集的性能瓶颈时,就应该考虑使用传统的 for 循环或新兴的迭代器助手来避免不必要的中间数组开销-7-9


sort()

1. sort()方法会改变原数组 ,而不是返回一个新数组。

2. 默认排序是将元素转换为字符串,然后比较它们的UTF-16代码单元值序列。

3. 对于数字排序,必须提供比较函数: (a, b) => a - b 用于升序, (a, b) => b - a 用于降序。

4. 比较函数应返回负数、零或正数,分别表示a在b之前、保持原顺序、a在b之后。


flatMap()方法使用示例详解


基本语法

array.flatMap(callback(currentValue, index, array), thisArg)
  • 参数:映射函数 (element, index, array) => newValue

  • 返回值:新数组(先映射后扁平化一级)


1. 基础使用示例

示例1:数组元素展开

const arr = [1, 2, 3];

// 每个元素变成包含自身和加1的数组
const result = arr.flatMap(x => [x, x + 1]);
console.log(result); // [1, 2, 2, 3, 3, 4]

// 对比 map() 的效果
const mapResult = arr.map(x => [x, x + 1]);
console.log(mapResult); // [[1, 2], [2, 3], [3, 4]]

示例2:过滤并展开

const numbers = [1, 2, 3, 4, 5];

// 只处理偶数,并展开为多个元素
const result = numbers.flatMap(x => {
  return x % 2 === 0 ? [x, x * 2] : [];
});
console.log(result); // [2, 4, 4, 8]

2. 实际应用场景

场景1:句子分词

const sentences = ["Hello world", "JavaScript is awesome", "Web development"];

// 将句子分割成单词并扁平化
const words = sentences.flatMap(sentence => sentence.split(' '));
console.log(words); 
// ["Hello", "world", "JavaScript", "is", "awesome", "Web", "development"]

// 对比:使用 map() + flat() 的等价写法
const words2 = sentences.map(sentence => sentence.split(' ')).flat();
console.log(words2); // 相同结果

场景2:数据清洗和转换

const data = [
  { name: "Alice", tags: ["js", "web"] },
  { name: "Bob", tags: ["python", "data"] },
  { name: "Charlie", tags: [] }
];

// 提取所有标签并扁平化
const allTags = data.flatMap(person => person.tags);
console.log(allTags); // ["js", "web", "python", "data"]

// 自动过滤空数组
const emptyFiltered = data.flatMap(person => person.tags || []);
console.log(emptyFiltered); // ["js", "web", "python", "data"]

场景3:矩阵操作

const matrix = [
  [1, 2],
  [3, 4],
  [5, 6]
];

// 矩阵转置的思路(简化版)
const flattened = matrix.flatMap(row => row);
console.log(flattened); // [1, 2, 3, 4, 5, 6]

// 每个元素乘以2并展开
const doubled = matrix.flatMap(row => row.map(x => x * 2));
console.log(doubled); // [2, 4, 6, 8, 10, 12]

3. 高级应用示例

示例1:嵌套数组处理

const nestedArrays = [[1], [2, 3], [4, 5, 6]];

// 展开并给每个元素添加索引信息
const result = nestedArrays.flatMap((subArray, index) => 
  subArray.map(value => ({ value, originalIndex: index }))
);
console.log(result);
// [
//   {value: 1, originalIndex: 0},
//   {value: 2, originalIndex: 1},
//   {value: 3, originalIndex: 1},
//   {value: 4, originalIndex: 2},
//   {value: 5, originalIndex: 2},
//   {value: 6, originalIndex: 2}
// ]

示例2:数据分组展开

const students = [
  { name: "Alice", scores: [85, 90] },
  { name: "Bob", scores: [78, 82] },
  { name: "Charlie", scores: [95] }
];

// 将每个学生和分数配对展开
const scoreDetails = students.flatMap(student =>
  student.scores.map(score => ({
    student: student.name,
    score: score
  }))
);
console.log(scoreDetails);
// [
//   {student: "Alice", score: 85},
//   {student: "Alice", score: 90},
//   {student: "Bob", score: 78},
//   {student: "Bob", score: 82},
//   {student: "Charlie", score: 95}
// ]

示例3:条件性展开

const products = [
  { name: "Laptop", categories: ["electronics", "computers"] },
  { name: "Book", categories: ["education"] },
  { name: "Phone", categories: ["electronics", "communication"] }
];

// 只展开电子类产品的分类
const electronicCategories = products.flatMap(product =>
  product.categories.includes("electronics") 
    ? product.categories 
    : []
);
console.log(electronicCategories); 
// ["electronics", "computers", "electronics", "communication"]

4. 与相关方法对比

flatMap() vs map() + flat()

const arr = [1, 2, 3];

// 这三种写法等价
const result1 = arr.flatMap(x => [x, x * 2]);
const result2 = arr.map(x => [x, x * 2]).flat();
const result3 = [].concat(...arr.map(x => [x, x * 2]));

console.log(result1); // [1, 2, 2, 4, 3, 6]
console.log(result2); // [1, 2, 2, 4, 3, 6]
console.log(result3); // [1, 2, 2, 4, 3, 6]

性能考虑

// flatMap() 通常比 map().flat() 性能更好
// 因为只需要遍历一次数组

const largeArray = Array.from({ length: 10000 }, (_, i) => i);

// 使用 flatMap(推荐)
console.time('flatMap');
const result1 = largeArray.flatMap(x => [x, x + 1]);
console.timeEnd('flatMap');

// 使用 map + flat
console.time('mapFlat');
const result2 = largeArray.map(x => [x, x + 1]).flat();
console.timeEnd('mapFlat');

5. 注意事项

注意1:只扁平化一级

const arr = [1, 2, 3];

// flatMap 只扁平化一级
const result = arr.flatMap(x => [[x, x * 2]]);
console.log(result); // [[1, 2], [2, 4], [3, 6]] - 仍然是嵌套数组

// 需要多级扁平化时
const deepResult = arr.flatMap(x => [[x, x * 2]]).flat();
console.log(deepResult); // [1, 2, 2, 4, 3, 6]

注意2:返回非数组值

const arr = [1, 2, 3];

// 如果回调函数返回非数组值,会自动包装成数组
const result = arr.flatMap(x => x * 2);
console.log(result); // [2, 4, 6] - 相当于 map(x => x * 2)

// 等价于
const result2 = arr.flatMap(x => [x * 2]);
console.log(result2); // [2, 4, 6]

总结

flatMap() 的核心优势:

  • 一次性操作:映射 + 扁平化一级

  • 代码简洁:避免 map().flat() 的链式调用

  • 自动过滤:返回空数组时自动过滤掉

  • 性能优化:通常比分开操作性能更好


使用场景:数据转换、文本处理、矩阵操作、条件性展开等需要同时进行映射和扁平化的场景。


Array.prototype.reduce()


Array.prototype.reduce() 是 JavaScript 中最强大的数组方法之一,用于将数组“缩减”为单个值(如求和、累加对象、扁平化等)。


它的签名如下:

1arr.reduce(callback(accumulator, currentValue, index, array), initialValue)

1. 参数详解

A. 回调函数 callback (必填)

这是每次迭代时执行的函数,它接收 4 个参数(通常只使用前两个):

  1. accumulator (累加器):
    • 累积的结果。
    • 它的值取决于上一次回调函数的返回值。
    • 如果是第一次调用:
      • 若提供了 initialValue,则它是 initialValue
      • 提供 initialValue,则它是数组的第一个元素
  2. currentValue (当前值):
    • 当前正在处理的数组元素。
    • 如果是第一次调用且提供 initialValue,则它是数组的第二个元素
  3. index (索引,可选):
    • 当前元素的索引。
    • 注意:如果未提供 initialValue,第一次回调时的索引是 1(因为第 0 个被当作累加器了);如果提供了 initialValue,第一次回调时的索引是 0
  4. array (原数组,可选):
    • 调用 reduce 的原始数组。

B. initialValue (可选)

  • 作用: 作为第一次调用回调函数时 accumulator 的初始值。
  • 重要性强烈建议始终提供此参数,以避免空数组报错和逻辑歧义。

2. initialValue 的行为差异 (核心重点)

是否提供 initialValue 会完全改变 reduce 的执行逻辑:

表格

场景 accumulator初始值 currentValue起始位置 循环次数 空数组[]的行为
提供 initialValue initialValue 索引 0 (第一个元素) 数组长度次 返回 initialValue (不报错)
不提供 initialValue 数组索引 0 的元素 索引 1 (第二个元素) 数组长度 - 1 次 抛出 TypeError

3. 代码示例

场景一:数字求和 (推荐写法)

const numbers = [1, 2, 3, 4];

// 提供 initialValue (0)
const sum = numbers.reduce((acc, curr) => {
  console.log(`累加器: ${acc}, 当前值: ${curr}`);
  return acc + curr;
}, 0); 

// 执行过程:
// 1. acc=0, curr=1 -> return 1
// 2. acc=1, curr=2 -> return 3
// 3. acc=3, curr=3 -> return 6
// 4. acc=6, curr=4 -> return 10

console.log(sum); // 10

场景二:如果不提供 initialValue

const numbers = [1, 2, 3, 4];

const sum = numbers.reduce((acc, curr) => {
  return acc + curr;
}); 
// 没有第二个参数

// 执行过程:
// 1. acc=1 (数组第0项), curr=2 (数组第1项) -> return 3
// 2. acc=3, curr=3 -> return 6
// 3. acc=6, curr=4 -> return 10

console.log(sum); // 10 (结果一样,但逻辑不同)

场景三:空数组的陷阱 (必须提供 initialValue)

const emptyArr = [];

// ❌ 错误:不提供 initialValue,空数组会报错
try {
  emptyArr.reduce((acc, curr) => acc + curr);
} catch (e) {
  console.error(e.message); 
  // 输出: "Reduce of empty array with no initial value"
}

// ✅ 正确:提供 initialValue,安全返回默认值
const safeSum = emptyArr.reduce((acc, curr) => acc + curr, 0);
console.log(safeSum); // 0

场景四:处理对象数组 (典型用例)

将数组转换为对象,必须提供初始对象作为 initialValue

const users = [
  { name: 'Alice', age: 25 },
  { name: 'Bob', age: 30 },
  { name: 'Charlie', age: 35 }
];

// 需求:计算总年龄
const totalAge = users.reduce((acc, user) => {
  return acc + user.age;
}, 0); // 初始值为 0

console.log(totalAge); // 90

// 需求:按名字分组 (初始值为空对象 {})
const grouped = users.reduce((acc, user) => {
  acc[user.name] = user.age;
  return acc;
}, {}); 

console.log(grouped); 
// { Alice: 25, Bob: 30, Charlie: 35 }

4. 总结最佳实践

  1. 永远提供 initialValue
    • 防止空数组报错。
    • 明确代码意图(例如求和从 0 开始,拼接字符串从 "" 开始,构建对象从 {} 开始)。
    • 确保类型安全(避免累加器在第一次迭代时意外变成数组中的某个复杂对象)。
  2. 明确参数名
    • 不要只用 (a, b),使用 (acc, curr) 或 (total, item) 等具有语义的名称。
  3. 注意返回值
    • reduce 的最终结果是最后一次回调函数的返回值。务必确保你的回调函数在所有路径下都 return 了正确的值。
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐