JS知识点总结三———数组(上)

        数组是 JavaScript 中最核心的数据结构之一,几乎每个项目都离不开它。本文全面总结 JavaScript 数组的核心知识,涵盖基础操作、常用方法、性能优化和现代技巧。

        本文全面总结了JavaScript数组的核心知识,包括创建方式(字面量、构造函数、ES6方法)、数组类型(一维/多维、稀疏数组、类数组对象、类型化数组)、常用方法(增删改查、转换迭代、连接切片等)、属性(length、prototype等)以及高级操作技巧。重点讲解了数组扁平化的多种实现方案(递归、ES6 flat、迭代等)和数组去重的方法(遍历、Set、filter组合等)。文章涵盖了从基础到进阶的数组知识点,适合开发者系统学习和参考使用,提升对JavaScript数组的理解和应用能力。


一、数组的创建

1.数组的基本概念

JavaScript 中的数组是一种特殊的对象,用于存储有序的数据集合。以下是数组的主要特点:

  • 可变长度:数组可以动态增长或缩小,无需预定义大小。

  • 零索引:第一个元素的索引为 0,最后一个为 length - 1。

  • 混合类型:数组可以存储不同类型的数据,如数字、字符串、对象,甚至其他数组。

  • 稀疏数组:数组可能包含空缺(“洞”),如 [1, , 3]。

  • 对象本质:数组是对象,typeof arr 返回 "object",但通过 Array.isArray(arr) 可确认其为数组。

数组在 JavaScript 引擎中优化为连续内存存储,适合快速访问,但若添加非数字属性或创建稀疏数组,性能可能下降。

2.数组创建

        正常来说,数组有三种创建方式,字面量创建,构造函数创建,以及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.一维和多维数组

(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() 等方法做数据处理

2.稀疏数组(Sparse Array)

        索引不连续,存在“空位”或“hole”,length 会自动根据最大索引变化,mapforEach 等不会遍历空位,底层降级为 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]
3.类数组对象(Array-like Object)

        拥有 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,直接报错,类型错误
4.类型化数组(TypedArray)

        用于存储和操作固定类型的二进制数据,长度固定、性能高、结构紧凑,不同于普通数组,它们只能存放特定类型的数据

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)

5.简单总结一下
分类 特征 是否连续索引 是否 length 自动更新 推荐使用
标准数组 最常见、性能好 ✅ 是 ✅ 是 ✅ 推荐
稀疏数组 索引不连续 ❌ 否 ✅ 是 ⚠️ 慎用
多维数组 嵌套数组模拟 ✅ 是 ✅ 是 ✅ 推荐
类数组对象 类似数组的对象 ✅ 是 ✅ 是 ⚠️ 转换后使用
类型化数组 二进制固定类型 ✅ 是 ✅ 是(固定长度) ✅ 高性能需求

三、数组方法总结

1.基础操作方法(会改变原数组)
方法 描述 示例 返回值
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]
2.查询与检测方法(不改变原数组)
方法 描述 示例 返回值
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
3.转换与迭代方法(不改变原数组)
方法 描述 示例 返回值
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]
4.连接与切片方法(不改变原数组)
方法 描述 示例 返回值
concat() 连接数组 [1].concat([2]) [1,2]
slice() 数组切片 [1,2,3].slice(1,3) [2,3]
join() 元素连接 [1,2,3].join('-') '1-2-3'
5.ES2023不可变方法(不改变原数组)
方法 描述 示例 返回值
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]
6.迭代器与创建方法
方法 描述 示例
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

四、数组属性

1.核心属性(所有数组都有)
属性 类型 描述 特点
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不存在)
2.原型链属性(从Array.prototype继承)
属性 类型 描述
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
3.类型化数组特有属性(Typed Arrays)
属性 类型 描述 示例值
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.递归实现

一般来说,对于嵌套结构的处理方法,第一个想到一般就是递归,太经典了,也很好用。

(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;
}
 2.es6新方法(flat)

最推荐的方法,简单好用

原生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]
3.迭代方案

一般是大数据集才会使用这种方法。

(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;
}

        与深度优先对比的话,广度优先保持层级信息的完整性,处理树形结构时更直观,适合需要保留层级关系的场景,内存占用略高于深度优先

4.其他方法

(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));
// 保留前两个对象


本篇文章主要介绍了数组的相关知识点。完结撒花!!!!

你可能感兴趣的:(JavaScript知识点总结,javascript,开发语言,ecmascript,前端)