数组是 JavaScript 中最核心的数据结构之一,几乎每个项目都离不开它。本文全面总结 JavaScript 数组的核心知识,涵盖基础操作、常用方法、性能优化和现代技巧。
本文全面总结了JavaScript数组的核心知识,包括创建方式(字面量、构造函数、ES6方法)、数组类型(一维/多维、稀疏数组、类数组对象、类型化数组)、常用方法(增删改查、转换迭代、连接切片等)、属性(length、prototype等)以及高级操作技巧。重点讲解了数组扁平化的多种实现方案(递归、ES6 flat、迭代等)和数组去重的方法(遍历、Set、filter组合等)。文章涵盖了从基础到进阶的数组知识点,适合开发者系统学习和参考使用,提升对JavaScript数组的理解和应用能力。
JavaScript 中的数组是一种特殊的对象,用于存储有序的数据集合。以下是数组的主要特点:
可变长度:数组可以动态增长或缩小,无需预定义大小。
零索引:第一个元素的索引为 0,最后一个为 length - 1。
混合类型:数组可以存储不同类型的数据,如数字、字符串、对象,甚至其他数组。
稀疏数组:数组可能包含空缺(“洞”),如 [1, , 3]。
对象本质:数组是对象,typeof arr 返回 "object",但通过 Array.isArray(arr) 可确认其为数组。
数组在 JavaScript 引擎中优化为连续内存存储,适合快速访问,但若添加非数字属性或创建稀疏数组,性能可能下降。
正常来说,数组有三种创建方式,字面量创建,构造函数创建,以及ES6中提供数组方法(主要是Array.of()、Array.from())创建。
// 字面量方式(推荐)
const arr =[] // 创建一个空数组
const arr2 = [1, 2, 3]; // 含元素的数组
const arr3 = ['a', , 'c']; // 稀疏数组(索引1处为empty)
// 构造函数
const emptyArr = new Array(); // 空数组 []
const sizedArr = new Array(5); // 创建长度为5的空槽数组 [empty × 5]
const numbers = new Array(1, 2, 3);
// Array.of() 解决构造函数歧义
const arr = Array.of(5); // [5]
const items = Array.of(1, 2); // [1, 2]
// Array.from()
// 字符串转数组
Array.from('hello'); // ['h','e','l','l','o']
// Set转数组
const set = new Set([1, 2, 2]);
Array.from(set); // [1, 2]
// 带映射函数
Array.from([1, 2, 3], x => x * 2); // [2, 4, 6]
(1)一维数组
索引连续、无空洞(hole)、最佳性能,适合大多数使用场景
const arr = [10, 20, 30];
console.log(arr[1]); // 20
console.log(arr.length); // 3
一般用于存储列表数据,以及 map/filter/reduce 等高阶数组操作和性能敏感场合(如前端渲染列表)
(2)多维数组
多维数组在 JS 中是“数组的数组”,JS 没有内建真正的二维/三维数组结构
const matrix = [
[1, 2],
[3, 4]
];
console.log(matrix[0][1]); // 2
一般用于表格数据、棋盘、坐标系,可结合 .map()
、.flat()
等方法做数据处理
索引不连续,存在“空位”或“hole”,length
会自动根据最大索引变化,map
、forEach
等不会遍历空位,底层降级为 HashMap 存储,性能较差
const sparse = [];
sparse[100] = 'hello';
console.log(sparse.length); // 101
console.log(sparse[50]); // undefined(不存在)
console.log(50 in sparse); // false
注意事项:
空洞不是 undefined
,而是“根本不存在该项”
遍历时常被跳过,可能导致逻辑 bug
let arr = [1, , 3]; // 稀疏数组,index 1 是空位
arr.map(x => x * 2); // [2, <1 empty item>, 6]
拥有 length
属性和按数字顺序的键名,不是真正的数组(没有 Array 的原型方法),可通过 Array.from
或展开运算符转为数组
const obj = {
0: 'a',
1: 'b',
length: 2
};
const realArray = Array.from(obj);
console.log(realArray); // ['a', 'b']
常见类数组对象
arguments
(函数参数对象)
NodeList
(DOM 查询返回结果)
HTMLCollection
(HTML 元素集合)
String
(可索引访问字符)
注意:
类数组不能直接使用数组方法
obj.map(x => x.toUpperCase()); // TypeError,直接报错,类型错误
用于存储和操作固定类型的二进制数据,长度固定、性能高、结构紧凑,不同于普通数组,它们只能存放特定类型的数据
const buffer = new ArrayBuffer(8); // 分配 8 字节
const intView = new Int32Array(buffer); // 每个 Int32 为 4 字节
intView[0] = 42;
console.log(intView[0]); // 42
常用 TypedArray 类型
类型 | 单元大小 | 描述 |
---|---|---|
Int8Array |
1字节 | 有符号整型 |
Uint8Array |
1字节 | 无符号整型 |
Float32Array |
4字节 | 单精度浮点 |
Float64Array |
8字节 | 双精度浮点 |
应用场景
音视频编解码
WebGL / 图像处理
与 C/C++ 模块交互(如 WebAssembly)
分类 | 特征 | 是否连续索引 | 是否 length 自动更新 | 推荐使用 |
---|---|---|---|---|
标准数组 | 最常见、性能好 | ✅ 是 | ✅ 是 | ✅ 推荐 |
稀疏数组 | 索引不连续 | ❌ 否 | ✅ 是 | ⚠️ 慎用 |
多维数组 | 嵌套数组模拟 | ✅ 是 | ✅ 是 | ✅ 推荐 |
类数组对象 | 类似数组的对象 | ✅ 是 | ✅ 是 | ⚠️ 转换后使用 |
类型化数组 | 二进制固定类型 | ✅ 是 | ✅ 是(固定长度) | ✅ 高性能需求 |
方法 | 描述 | 示例 | 返回值 |
---|---|---|---|
push() | 尾部添加元素 | [1,2].push(3) |
新长度 3 |
pop() | 尾部移除元素 | [1,2].pop() |
被移除元素 2 |
unshift() | 头部添加元素 | [1,2].unshift(0) |
新长度 3 |
shift() | 头部移除元素 | [1,2].shift() |
被移除元素 1 |
splice() | 任意位置增删 | [1,2,3].splice(1,1,'a') |
被删除元素 [2] |
sort() | 排序数组 | [3,1,2].sort() |
排序后数组 [1,2,3] |
reverse() | 反转数组 | [1,2,3].reverse() |
反转后数组 [3,2,1] |
fill() | 填充元素 | new Array(3).fill(0) |
[0,0,0] |
copyWithin() | 数组内复制 | [1,2,3,4].copyWithin(0,2) |
[3,4,3,4] |
方法 | 描述 | 示例 | 返回值 |
---|---|---|---|
includes() | 是否包含元素 | [1,2,3].includes(2) |
true |
indexOf() | 元素首次索引 | [1,2,2].indexOf(2) |
1 |
lastIndexOf() | 元素最后索引 | [1,2,2].lastIndexOf(2) |
2 |
find() | 查找首个匹配元素 | [1,2,3].find(x => x>1) |
2 |
findIndex() | 查找首个匹配索引 | [1,2,3].findIndex(x => x>1) |
1 |
findLast() (ES2023) | 查找末个匹配元素 | [1,2,3].findLast(x => x<3) |
2 |
findLastIndex() (ES2023) | 查找末个匹配索引 | [1,2,3].findLastIndex(x => x<3) |
1 |
at() | 索引访问 | [1,2,3].at(-1) |
3 |
some() | 是否有元素满足条件 | [1,2,3].some(x => x>2) |
true |
every() | 是否所有元素满足 | [1,2,3].every(x => x>0) |
true |
方法 | 描述 | 示例 | 返回值 |
---|---|---|---|
map() | 映射新数组 | [1,2,3].map(x => x*2) |
[2,4,6] |
filter() | 过滤元素 | [1,2,3].filter(x => x>1) |
[2,3] |
forEach() | 遍历元素 | [1,2].forEach(x => console.log(x)) |
undefined |
reduce() | 从左归并 | [1,2,3].reduce((s,v) => s+v) |
6 |
reduceRight() | 从右归并 | ['a','b'].reduceRight((s,v) => s+v) |
'ba' |
flat() | 数组扁平化 | [1,[2,[3]]].flat(2) |
[1,2,3] |
flatMap() | 映射+扁平化 | [1,2].flatMap(x => [x, x*2]) |
[1,2,2,4] |
方法 | 描述 | 示例 | 返回值 |
---|---|---|---|
concat() | 连接数组 | [1].concat([2]) |
[1,2] |
slice() | 数组切片 | [1,2,3].slice(1,3) |
[2,3] |
join() | 元素连接 | [1,2,3].join('-') |
'1-2-3' |
方法 | 描述 | 示例 | 返回值 |
---|---|---|---|
toSorted() | 排序数组 | [3,1,2].toSorted() |
[1,2,3] |
toReversed() | 反转数组 | [1,2,3].toReversed() |
[3,2,1] |
toSpliced() | 增删元素 | [1,2,3].toSpliced(1,1) |
[1,3] |
with() | 替换元素 | [1,2,3].with(1,9) |
[1,9,3] |
方法 | 描述 | 示例 |
---|---|---|
keys() | 索引迭代器 | for(const key of [1,2].keys()) |
values() | 值迭代器 | for(const value of [1,2].values()) |
entries() | 键值对迭代器 | for(const [i,v] of [1,2].entries()) |
Array.from() | 类数组转数组 | Array.from('abc') → ['a','b','c'] |
Array.of() | 参数转数组 | Array.of(5) → [5] |
Array.isArray() | 检测数组 | Array.isArray([]) → true |
属性 | 类型 | 描述 | 特点 |
---|---|---|---|
length |
数字 | 数组中元素的数量 | • 可写(可修改) • 自动更新 • 只计算实际元素 |
index(索引) | 任意 | 通过数字索引访问元素 | • 从0开始 • 最大索引 2³²-1 • 可创建稀疏数组 |
const arr = ['a', 'b', 'c'];
// length属性
console.log(arr.length); // 3
// 修改length
arr.length = 2; // 现在 arr = ['a', 'b']
// 索引访问
console.log(arr[0]); // 'a'
console.log(arr[1]); // 'b'
console.log(arr[2]); // undefined
// 稀疏数组
const sparse = [1, , 3];
console.log(sparse.length); // 3
console.log(1 in sparse); // false (索引1不存在)
属性 | 类型 | 描述 |
---|---|---|
constructor |
函数 | 指向创建数组的构造函数(通常是 Array ) |
prototype |
对象 | 包含所有数组方法的原型对象 |
const arr = [1, 2, 3];
// constructor属性
console.log(arr.constructor === Array); // true
// prototype上的方法
console.log(arr.map); // ƒ map() { [native code] }
// 添加自定义方法(谨慎使用)
Array.prototype.last = function() {
return this[this.length - 1];
};
console.log(arr.last()); // 3
属性 | 类型 | 描述 | 示例值 |
---|---|---|---|
BYTES_PER_ELEMENT |
数字 | 每个元素占用的字节数 | Int8Array: 1 Float64Array: 8 |
buffer |
ArrayBuffer | 关联的二进制数据缓冲区 | - |
byteLength |
数字 | 数组的总字节长度 | new Int16Array(10).byteLength // 20 |
byteOffset |
数字 | 在缓冲区中的字节偏移量 | new Int32Array(buffer, 4).byteOffset // 4 |
// 创建类型化数组
const buffer = new ArrayBuffer(16);
const int32Array = new Int32Array(buffer);
console.log(int32Array.BYTES_PER_ELEMENT); // 4
console.log(int32Array.byteLength); // 16
console.log(int32Array.byteOffset); // 0
console.log(int32Array.buffer === buffer); // true
数组扁平化(Flattening) 是将一个嵌套多层的数组结构转换成一个单层数组的过程。例如:
const arr = [1, [2, [3, [4]], 5]];
// 进行扁平化处理后
// [1, 2, 3, 4, 5]
下面介绍一下常见的实现数组扁平化管理的方法。
一般来说,对于嵌套结构的处理方法,第一个想到一般就是递归,太经典了,也很好用。
(1)经典递归方法
function flatten(arr) {
// 结果数组初始化
let result = [];
// 遍历每个元素
for (const item of arr) {
// 递归处理数组元素
if (Array.isArray(item)) {
result = result.concat(flatten(item));
}
// 直接添加非数组元素
else {
result.push(item);
}
}
return result;
}
(2)使用reduce迭代方法
虽然reduce是一个迭代方法,但是要实现数组扁平化,得将这个方法放在递归函数中。
function flatten(arr) {
return arr.reduce((acc, val) =>
acc.concat(Array.isArray(val) ? flatten(val) : val),
[]);
}
(3)尾递归优化
尾递归是函数式编程中的优化技术,能有效避免栈溢出问题。
function flatten(arr, result = []) {
for (const item of arr) {
Array.isArray(item)
? flatten(item, result)
: result.push(item);
}
return result;
}
最推荐的方法,简单好用。
原生API简洁高效,支持深度控制,浏览器优化性能优异
flatMap
实现映射+扁平化的原子操作
// 基本扁平化
[1, [2, [3]]].flat(2); // [1, 2, 3]
// 无限深度扁平化
[1, [2, [3, [4]]]].flat(Infinity); // [1, 2, 3, 4]
// 映射+扁平化组合操作
[1, 2, 3].flatMap(x => [x, x*2]); // [1, 2, 2, 4, 3, 6]
一般是大数据集才会使用这种方法。
(1)深度优先栈
当处理超大规模数据时,基于栈的迭代方案是非常好的选择
function flatten(arr) {
const stack = [...arr];
const result = [];
while (stack.length) {
const next = stack.pop();
if (Array.isArray(next)) {
stack.push(...next);
} else {
result.unshift(next);
}
}
return result;
}
这个算法的核心流程为:初始化栈存储待处理元素,循环弹出栈顶元素,若为数组则展开后重新入栈,非数组元素则通过unshift
添加至结果集,从而确保元素原始顺序得以保持。
从性能角度分析,该方法优势明显:首先,因无递归调用,故不存在递归深度限制,可有效避免因深度嵌套导致的栈溢出风险;其次,内存占用相对可控,对处理百万级元素的大规模数据场景具有较好的适用性,能从容应对复杂数据结构的扁平化需求。
(2)广度优先队列
广度优先策略在处理分层数据时更具优势。
function flattenBFS(arr) {
const queue = [...arr];
const result = [];
while (queue.length) {
const next = queue.shift();
if (Array.isArray(next)) {
queue.push(...next);
} else {
result.push(next);
}
}
return result;
}
与深度优先对比的话,广度优先保持层级信息的完整性,处理树形结构时更直观,适合需要保留层级关系的场景,内存占用略高于深度优先
(1) JSON转换法(纯数据对象)
不太推荐这种方法,弊端太多,不好用。
对于纯数据(不含函数、Symbol等特殊类型)的数组,JSON转换提供了一种巧妙的解决方案。
function flattenJSON(arr) {
return JSON.parse(
`[${JSON.stringify(arr)
.replace(/\[|\]/g, '')
.replace(/,\s*]/g, '')}]`
);
}
该方法通过将数组转换为 JSON 字符串,利用正则表达式移除所有方括号来实现数组扁平化,并在处理过程中解决边界逗号问题,最终将处理后的字符串重新解析为数组。
注意事项:
可能会丢失undefined和函数
无法处理循环引用
日期对象会转为字符串
仅适用于纯数据场景
(2)toString 转换 (纯数字/字符串数组)
function flattenToString(arr) {
return arr.toString()
.split(',')
.map(item => isNaN(item) ? item : Number(item));
}
// [1, [2, 'a']] → "1,2,a" → [1, 2, "a"]
(3)Generator惰性加载(大数据流处理)
Generator函数提供了一种惰性求值的解决方案,特别适合处理超大规模数据集。
function* flattenGen(arr) {
for (const item of arr) {
if (Array.isArray(item)) {
yield* flattenGen(item);
} else {
yield item;
}
}
}
核心优势:
按需生成数据,内存占用极小
支持分块处理大规模数据
可与异步操作完美结合
提供灵活的数据消费方式
数组去重在实际应用场景中也很多,然后面试题也很喜欢问这个,所以在这里简单介绍一下。
1. 遍历数组
既然通过遍历(遍历方法有很多,循环、reduce属性等都可以)能获得每个数组中的所有数据,那做个去重是不是也是so easy。这种方法做数组去重,可以应用在很多场景中,中间的if条件可以根据你的需求去添加,下面只是一个简单的示例。
arr=[1,5,1,3,5,4,3,9,8]
let newArr = [];
for (let i=0;i
2.ES6的新方法Set()
这种方法写起来很简单,很推荐使用这种方式,像前面哪种对简单数组的去重,一行代码就可以搞定。
// 一行代码解决基础类型去重
const uniqueArray = [...new Set(originalArray)];
// 示例
const numbers = [1, 2, 2, 3, 4, 4, 5];
console.log([...new Set(numbers)]); // [1, 2, 3, 4, 5]
3.filter+方法(可以是indexOf、findIndex、set)
(1)filter+indexOf
使用filter+indexOf可以对简单数组去重
function uniqueArray(arr) {
return arr.filter((item, index, array) => {
return array.indexOf(item) === index;
});
}
// 使用示例
const numbers = [1, 2, 2, 3, 4, 4, 5];
console.log(uniqueArray(numbers)); // [1, 2, 3, 4, 5]
(2)filter+set
对于复杂的对象数组也可以使用这种方法,通过使用filter和Set结合可以达到去重的效果。
function uniqueBy(arr, prop) {
const seen = new Set();
return arr.filter(item =>
seen.has(item[prop]) ? false : seen.add(item[prop])
);
}
// 使用示例
const users = [
{id: 1, name: 'John'},
{id: 2, name: 'Jane'},
{id: 1, name: 'Johnny'} // 相同 ID
];
console.log(uniqueBy(users, 'id'));
// 输出: [{id: 1, name: 'John'}, {id: 2, name: 'Jane'}]
(3)filter+findIndex
这种方法一般也是用于对象数组去重
function uniqueWithFindIndex(arr, comparator = (a, b) => a === b) {
return arr.filter((item, index) =>
arr.findIndex(i => comparator(i, item)) === index
);
}
// 使用示例
const users = [
{id: 1, name: 'John'},
{id: 2, name: 'Jane'},
{id: 1, name: 'Johnny'} // 相同 ID
];
// 基于 ID 去重
console.log(uniqueWithFindIndex(users, (a, b) => a.id === b.id));
// 保留前两个对象
本篇文章主要介绍了数组的相关知识点。完结撒花!!!!