数组的基本定义

JavaScript 数组是一种用于存储有序数据集合的特殊对象,可包含任意类型的元素(如数字、字符串、对象、布尔值等),并通过从 0 开始的数字索引访问元素‌


核心特性

动态长度:数组长度可动态增减,无需预先声明固定大小‌

元素类型灵活:支持混合存储不同类型的数据

const arr = [1, "text", true, { key: "value" }, [2, 3]]; 

其中包含数字、字符串、布尔值、对象和嵌套数组‌

连续内存结构‌:数组在内存中表现为一段‌连续的内存地址,数组名指向首地址‌

与普通对象的区别与联系

联系:数组本质是由对象派生而来,可调用部分对象方法(如 toString)‌
区别索引顺序:数组元素按严格数字顺序排列,对象属性无序‌。操作方法:数组提供专有方法(如 pushmap)用于操作元素集合,对象无此类内置方法‌

使用场景

数据列表存储:适用于需保持顺序的数据集合(如排行榜、表格行数据)‌
批量操作:通过循环或高阶函数(如 forEachfilter)高效处理多元素‌


注意事项

性能优化:频繁增删元素时,优先使用 push/pop(操作尾部)而非 unshift/shift(操作头部),避免索引重排开销‌

基本概念

数据集合‌:数组是‌相同类型元素‌的集合,所有元素存储于‌连续的内存空间中,元素类型可以是整型、字符型等任意一致的数据类型‌
元素约束:数组中的元素个数不能为0,且必须在定义时明确大小(C99标准前)或通过变长数组动态指定(C99标准后)‌

数组的元素组成

元素标识‌:数组元素是‌变量‌,通过‌数组名 + 下标唯一标识。例如 arr 表示数组 arr 的第一个元素‌
访问规则‌:元素需先通过数组定义分配内存空间后,才能逐个通过下标访问,‌不支持直接引用整个数组
下标范围:数组下标从 0 开始,依次递增至数组长度减1。例如长度为 n 的数组,最大有效下标为 n-1索引作用‌:下标用于定位元素在数组中的‌顺序位置,类似于数学集合中的索引概念,确保数据的有序性和快速访问‌

数组操作

JavaScript 数组使用方括号 [] 表示,元素以逗号分隔,可包含任意类型的数据(如数值、字符串、对象、其他数组等)‌

// 混合类型数组  
const mixedArr = [1, "text", true, { key: "value" }, [4, 5]];  
// 空数组  
const emptyArr = [];  

数组的创建方式

字面量方式 直接使用方括号 [] 定义元素,是最简洁、常用的方式‌

const arr1 = [1, 2, 3];  
const arr2 = ["a", "b", "c"];  

构造函数方式 使用 new Array()Array() 创建,但需注意参数特性

单一数值参数:表示数组长度(生成空槽数组)‌

多个参数或非数值参数:作为数组元素‌

const arr3 = new Array(3);       // [empty × 3](稀疏数组)  
const arr4 = new Array(1, "a");  // [1, "a"]  

其他创建方式

扩展操作符:复制或合并现有数组‌

const arr5 = [...arr1, ...arr2];  // [1, 2, 3, "a", "b", "c"]  

Array.of():明确将参数作为元素,避免构造函数歧义‌

const arr6 = Array.of(5);

Array.from():将可迭代对象(如字符串、Set)转换为数组‌

const arr7 = Array.from("abc");

元素访问与长度获取

索引访问:通过下标(从 0 开始)直接获取元素。

const fruits = ["apple", "banana"]; 
console.log(fruits); 
console.log(fruits.length);

首尾元素:通过 arrarr[arr.length-1] 获取首尾元素

数组长度:使用 length 属性获取元素数量。

遍历数组元素

for 循环:通过索引遍历。

for (let i = 0; i < fruits.length; i++) {  
  console.log(fruits[i]);  
}

forEach 方法:遍历元素并执行回调。

fruits.forEach((item, index) => {  
  console.log(index, item);  
});

高阶函数:如 mapfilterreduce 等。

onst lengths = fruits.map(fruit => fruit.length); // 计算长度 ‌
const longFruits = fruits.filter(fruit => fruit.length > 5); // 筛选结果 ‌

元素查找与筛选

按值查找

indexOf():返回第一个匹配的索引(无匹配返回 -1)。

lastIndexOf():返回最后一个匹配的索引 ‌

const idx = fruits.indexOf("banana");

条件查找

find():返回第一个符合条件的元素。

findIndex():返回第一个符合条件的索引

const item = fruits.find(fruit => fruit.startsWith("b"));

批量筛选filter() 返回符合条件的新数组

嵌套数组的检索

需在数组中查找子数组,可通过以下方式:

循环检查:使用 for 循环结合 Array.isArray() 判断元素是否为数组。

const arr = [1, [2, 3], 4];  
for (let i = 0; i < arr.length; i++) {  
  if (Array.isArray(arr[i])) {  
    console.log("子数组:", arr[i]); // [2, 3] ‌:ml-citation{ref="4,6" data="citationList"}  
  }  
}  

批量筛选:通过 filter() 提取所有子数组。

const subArrays = arr.filter(item => Array.isArray(item));

数组的扩展操作

ES6 扩展运算符

复制数组 用扩展运算符可快速创建数组的浅拷贝,避免引用传递问题‌

const original = [1, 2, 3];  
const copy = [...original]; // 新数组,与原数组无引用关系  

合并数组

合并多个数组时无需调用 concat,直接通过扩展运算符实现‌

const arr1 = [1, 2], arr2 = [3, 4];  
const combined = [...arr1, ...arr2]; // [1, 2, 3, 4]  

函数参数传递

将数组元素展开为函数参数,替代 apply 方法‌


function sum(a, b, c) { return a + b + c; }  
const nums = [1, 2, 3];  
sum(...nums); // 6  

动态添加元素
在现有数组中插入新元素,保持代码简洁性‌

const base = [2, 3];  
const newArr = [1, ...base, 4]; // [1, 2, 3, 4] 

实例方法的扩展

数据操作基础方法

增删元素

push()/pop():尾部操作元素‌

unshift()/shift():头部操作元素‌

splice():任意位置增删或替换元素‌

let arr = [1, 2];  
arr.push(3); // [1, 2, 3]  
arr.splice(1, 1, 4); // [1, 4, 3]  

高阶函数处理数据

遍历与转换

map():映射新数组‌

filter():筛选符合条件的元素‌

reduce():累计计算为单个值‌

const nums = [1, 2, 3];  
const doubled = nums.map(x => x * 2); // [2, 4, 6]  
const sum = nums.reduce((acc, cur) => acc + cur, 0); // 6 

查找与判断

find():返回首个匹配元素‌

some()/every():判断元素是否满足条件‌

const users = [{id: 1}, {id: 2}];  
const user = users.find(u => u.id === 2); // {id: 2}  

高级技巧

解构赋值结合扩展运算符
提取数组首尾元素时,可配合扩展运算符快速实现‌

const [first, ...rest] = [1, 2, 3];  
console.log(first); // 1  
console.log(rest);  // [2, 3]  

Math 函数结合
直接传递数组参数进行数学计算‌

const nums = [5, 2, 8];  
Math.max(...nums); // 8  

数组的增删改查

添加元素

末尾添加push():添加一个或多个元素到数组末尾,返回新数组长度。

const arr = [1, 2];  
arr.push(3); // 返回 3,数组变为 [1, 2, 3]  

支持链式调用:arr.push(4).push(5)(需注意返回值变化)‌


开头添加unshift():添加一个或多个元素到数组开头,返回新数组长度。

arr.unshift(0); // 返回 4,数组变为 [0, 1, 2, 3] 

性能较低,需移动所有元素索引‌

中间插入splice(startIndex, 0, newElement):从指定位置插入元素,不删除原元素。

arr.splice(2, 0, "a", "b"); // 数组变为 [0, 1, "a", "b", 2, 3] 

删除元素

末尾删除pop():删除并返回最后一个元素。

const last = arr.pop(); // last = 3,数组变为 [0, 1, "a", "b", 2] 

开头删除shift():删除并返回第一个元素。

const first = arr.shift(); // first = 0,数组变为 [1, "a", "b", 2] 

指定位置删除

splice(startIndex, deleteCount):删除指定数量元素,返回被删除元素的数组

const deleted = arr.splice(1, 2); // deleted = ["a", "b"],数组变为 [1, 2] 

修改元素

直接索引赋值 通过下标直接修改元素

使用 splice 替换元素 splice(startIndex, deleteCount, newElement):删除并插入新元素。

arr.splice(0, 1, "start"); // 删除第一个元素并插入 "start",数组变为 ["start", "newValue"] ‌ 

查询元素

索引访问通过下标直接访问元素:

const elem = arr[1]; 

条件查询includes():判断是否包含某元素,返回布尔值。

arr.includes("start"); // true 

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

const result = arr.find(item => item === "start"); // "start" ‌  

遍历查询forEach()map() 等方法遍历数组处理数据‌

数组的拷贝

浅拷贝与深拷贝的核心差异在于是否共享嵌套数据的引用。浅拷贝适合轻量级场景,而深拷贝需权衡性能与数据独立性需求,优先选择稳定实现

浅拷贝详解

浅拷贝(Shallow Copy)指仅复制数组的顶层元素,若元素为基本类型(如数字、字符串),则直接复制值;若为引用类型(如嵌套数组、对象),则复制其内存地址,导致新旧数组共享嵌套数据‌

核心特点

  • 修改顶层基本类型元素时,原数组不受影响;

  • 修改嵌套的引用类型元素时,原数组和新数组会同步变化‌

实现方法

slice()‌ 截取数组片段返回新数组,嵌套引用类型共享地址。let copy = arr.slice()

const originalArray = [1, 2, 3, { name: 'obj' }];
const copiedArray = originalArray.slice(); // 浅拷贝

copiedArray[0] = 99;         // 修改基本类型(不影响原数组)
copiedArray[3].name = 'new'; // 修改对象属性(会影响原数组对象)

console.log(originalArray); // [1, 2, 3, { name: 'new' }]
console.log(copiedArray);   // [99, 2, 3, { name: 'new' }]
  1. 无参调用slice() 不传参数时,默认截取整个数组(0length-1

  2. 独立数组:新数组与原数组的引用不同,但对象元素共享引用

  3. 非破坏性:原数组不会被修改

扩展运算符 ...‌ 展开数组生成新副本,仅顶层独立。 let copy = [...arr]

const originalArray = [1, 2, { name: 'obj' }, [3, 4]];
const copiedArray = [...originalArray]; // 浅拷贝

copiedArray[0] = 99;          // 修改基本类型(不影响原数组)
copiedArray[2].name = 'new';  // 修改对象属性(会影响原数组对象)
copiedArray[3].push(5);       // 修改嵌套数组(会影响原数组)

console.log(originalArray); 
// [1, 2, { name: 'new' }, [3, 4, 5]]
console.log(copiedArray);   
// [99, 2, { name: 'new' }, [3, 4, 5]]
  1. 语法简洁:相比 slice() 更直观易读

  2. 顶层独立:新数组与原数组内存地址不同,但对象/数组元素仍共享引用

  3. 非破坏性:原数组保持不变

  4. 可结合其他元素:可在拷贝时插入新元素

应用场景


//合并数组:
const merged = [...arr1, ...arr2];

//添加加元素同时拷贝:
const withNewItem = ['header', ...original, 'footer'];

//函数参数传递: 
Math.max(...[1, 5, 3]); // 等价于 Math.max(1,5,3)

Object.assign() 合并对象生成新数组,仅首层拷贝。 let copy = Object.assign([], arr)

const originalArray = [1, 2, { name: 'obj' }, [3, 4]];
const copiedArray = Object.assign([], originalArray); // 浅拷贝

copiedArray[0] = 99;          // 修改基本类型(不影响原数组)
copiedArray[2].name = 'new';  // 修改对象属性(会影响原数组)
copiedArray[3].push(5);       // 修改嵌套数组(会影响原数组)

console.log(originalArray); 
// [1, 2, { name: 'new' }, [3, 4, 5]]
console.log(copiedArray);   
// [99, 2, { name: 'new' }, [3, 4, 5]]
  1. 对象合并原理:通过将空数组作为目标对象,原数组元素作为源对象进行属性复制

  2. 首层拷贝:新数组与原数组引用不同,但对象元素仍共享内存地址

  3. 适用场景:主要用于对象合并,数组拷贝时不如扩展运算符直观

应用场景

更适合需要合并对象属性的场景:

// 对象合并示例
const obj1 = { a: 1 };
const obj2 = { b: 2 };
const mergedObj = Object.assign({}, obj1, obj2); // { a:1, b:2 }

concat()‌ 合并空数组生成新副本,效果类似slice()。 let copy = arr.concat()

const originalArray = [1, 2, { name: 'obj' }, [3, 4]];
const copiedArray = originalArray.concat(); // 浅拷贝
// 或合并空数组更直观
const copiedArray2 = [].concat(originalArray);

copiedArray[0] = 99;          // 修改基本类型(不影响原数组)
copiedArray[2].name = 'new';  // 修改对象属性(会影响原数组)
copiedArray[3].push(5);       // 修改嵌套数组(会影响原数组)

console.log(originalArray); 
// [1, 2, { name: 'new' }, [3, 4, 5]]
console.log(copiedArray);   
// [99, 2, { name: 'new' }, [3, 4, 5]]
  1. 无参调用concat() 不传参数时,默认合并空数组生成副本

  2. 首层独立:新数组与原数组引用不同,但对象/数组元素共享内存地址

  3. 非破坏性:原数组保持原样

  4. 历史兼容性:支持 ES3+ 环境,比扩展运算符兼容性更好

应用场景

  1. 简单数据复制

    • 适用于仅需复制顶层数据的场景(如传递配置参数)。

  2. 性能优先

    • 浅拷贝开销低,适合对性能敏感且无需隔离嵌套数据的场景‌。

  3. 临时数据共享

    • 需要多个数组共享同一组嵌套数据时(如状态快照)‌

深拷贝详解

深拷贝(Deep Copy)指递归复制数组的所有层级元素,包括嵌套的引用类型(如对象、数组),生成与原数组完全独立的新数组。修改深拷贝后的数组不会影响原数组,二者无任何内存地址共享‌

核心特点

  • 所有层级元素独立,包括基本类型和引用类型;

  • 支持复杂数据结构(如多层嵌套、特殊对象类型)的完全隔离‌

实现方法

JSON.parse(JSON.stringify())‌ 通过序列化与反序列化生成新数组,支持大部分数据类型。

适用场景 简单数据结构的深拷贝(无函数、undefined

局限性 忽略函数、undefinedSymbol,无法处理循环引用,不支持Date等特殊对象‌

const originalArray = [1, { name: 'obj' }, [3, 4], new Date()];
const deepCopiedArray = JSON.parse(JSON.stringify(originalArray));

deepCopiedArray[1].name = 'new';  // 修改对象属性
deepCopiedArray[2].push(5);       // 修改嵌套数组

console.log(originalArray); 
// [1, { name: 'obj' }, [3, 4], Date对象]
console.log(deepCopiedArray);   
// [1, { name: 'new' }, [3, 4, 5], ISO日期字符串]


// 核心限制
const problemArray = [
  function(){},          // 函数 → 丢失
  undefined,             // undefined → 丢失
  Symbol('test'),        // Symbol → 丢失
  new Date(),            // Date → 转为字符串
  Infinity,              // Infinity → null
  /regex/g,              // 正则表达式 → 空对象
  new Error('test')      // 错误对象 → 空对象
];
const result = JSON.parse(JSON.stringify(problemArray));
// [null, null, null, "2024-03-07T...", null, {}, {}]
  1. 完全独立副本:新数组与所有嵌套元素均无引用关系

  2. 数据序列化:通过 JSON 格式转换实现深拷贝

  3. 适用场景:适合处理 JSON 安全数据结构(不包含函数/Symbol 等特殊类型)

递归遍历 手动遍历数组元素,递归复制引用类型。

适用场景 自定义深拷贝逻辑,处理特殊需求

限性 需处理循环引用和复杂类型,代码复杂度高,性能较低‌

function deepClone(arr) {
  // 基础类型直接返回
  if (typeof arr !== 'object' || arr === null) return arr
  
  // 处理特殊对象类型
  if (arr instanceof Date) return new Date(arr)
  if (arr instanceof RegExp) return new RegExp(arr)

  // 创建新容器(区分数组和对象)
  const clone = Array.isArray(arr) ? [] : {}

  // 递归拷贝每个元素
  for (let key in arr) {
    if (arr.hasOwnProperty(key)) {
      clone[key] = deepClone(arr[key])
    }
  }
  return clone
}

// 使用示例
const original = [1, { a: 2 }, [3, new Date()]]
const cloned = deepClone(original)

cloned[1].a = 99       // 修改对象属性
cloned[2][0] = 300     // 修改嵌套数组
console.log(original)  // [1, {a:2}, [3, Date对象]] 保持原样

const obj = { a: 1 }
obj.self = obj
// 基础版本会栈溢出,需增加 WeakMap 跟踪

类型识别机制

  • 优先处理 DateRegExp 等特殊对象

  • 自动区分数组和普通对象

  • 保留原型链特性(通过 constructor 可扩展)

增强版方案(含循环引用处理)

function deepClone(obj, hash = new WeakMap()) {
  if (obj === null) return null
  if (typeof obj !== 'object') return obj
  if (obj instanceof Date) return new Date(obj)
  if (obj instanceof RegExp) return new RegExp(obj)
  
  // 检查循环引用
  if (hash.has(obj)) return hash.get(obj)

  const clone = new obj.constructor()
  hash.set(obj, clone)

  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      clone[key] = deepClone(obj[key], hash)
    }
  }
  return clone
}

structuredClone() 原生API,支持复杂类型(如DateMapSet)和循环引用,性能较好。

适用场景 现代浏览器环境,需处理复杂类型或循环引用

限性 部分旧浏览器不支持,无法复制函数或原型链属性‌

const originalArray = [1, { name: 'obj' }, [3, 4], new Date(), new Set([5,6])];
const deepCopiedArray = structuredClone(originalArray);

// 修改拷贝后的数据
deepCopiedArray[1].name = 'new';
deepCopiedArray[2].push(5);
deepCopiedArray[3].setFullYear(2025);

console.log(originalArray);
// [1, {name:'obj'}, [3,4], 原日期对象, Set {5,6}]

console.log(deepCopiedArray);
// [1, {name:'new'}, [3,4,5], 2025年日期对象, Set {5,6}]

// 兼容旧环境的备用方案
function safeStructuredClone(obj) {
  if (typeof structuredClone === 'function') {
    return structuredClone(obj);
  }
  // 备用方案(简易深拷贝)
  return JSON.parse(JSON.stringify(obj));
}
  1. 现代浏览器原生支持:Chrome 98+/Firefox 94+/Safari 15.4+ 等现代浏览器支持

  2. 完整类型支持

    • ✅ 处理循环引用

    • ✅ 保留 Date/RegExp/Map/Set 等特殊类型

    • ✅ 支持 ArrayBuffer/Blob 等二进制类型

  3. 深度克隆:所有层级数据均独立,无引用共享