
基础知识复习- 关于Js中数组的详细学习
数组的基本定义
JavaScript 数组是一种用于存储有序数据集合的特殊对象,可包含任意类型的元素(如数字、字符串、对象、布尔值等),并通过从 0
开始的数字索引访问元素
核心特性
动态长度:数组长度可动态增减,无需预先声明固定大小
元素类型灵活:支持混合存储不同类型的数据
const arr = [1, "text", true, { key: "value" }, [2, 3]];
其中包含数字、字符串、布尔值、对象和嵌套数组
连续内存结构:数组在内存中表现为一段连续的内存地址,数组名指向首地址
与普通对象的区别与联系
联系:数组本质是由对象派生而来,可调用部分对象方法(如 toString
)
区别:索引顺序:数组元素按严格数字顺序排列,对象属性无序。操作方法:数组提供专有方法(如 push
、map
)用于操作元素集合,对象无此类内置方法
使用场景
数据列表存储:适用于需保持顺序的数据集合(如排行榜、表格行数据)
批量操作:通过循环或高阶函数(如 forEach
、filter
)高效处理多元素
注意事项
性能优化:频繁增删元素时,优先使用 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);
首尾元素:通过 arr
和 arr[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);
});
高阶函数:如 map
、filter
、reduce
等。
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' }]
无参调用:
slice()
不传参数时,默认截取整个数组(0
到length-1
)独立数组:新数组与原数组的引用不同,但对象元素共享引用
非破坏性:原数组不会被修改
扩展运算符 ...
展开数组生成新副本,仅顶层独立。 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]]
语法简洁:相比
slice()
更直观易读顶层独立:新数组与原数组内存地址不同,但对象/数组元素仍共享引用
非破坏性:原数组保持不变
可结合其他元素:可在拷贝时插入新元素
应用场景
//合并数组:
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]]
对象合并原理:通过将空数组作为目标对象,原数组元素作为源对象进行属性复制
首层拷贝:新数组与原数组引用不同,但对象元素仍共享内存地址
适用场景:主要用于对象合并,数组拷贝时不如扩展运算符直观
应用场景
更适合需要合并对象属性的场景:
// 对象合并示例
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]]
无参调用:
concat()
不传参数时,默认合并空数组生成副本首层独立:新数组与原数组引用不同,但对象/数组元素共享内存地址
非破坏性:原数组保持原样
历史兼容性:支持 ES3+ 环境,比扩展运算符兼容性更好
应用场景
简单数据复制
适用于仅需复制顶层数据的场景(如传递配置参数)。
性能优先
浅拷贝开销低,适合对性能敏感且无需隔离嵌套数据的场景。
临时数据共享
需要多个数组共享同一组嵌套数据时(如状态快照)
深拷贝详解
深拷贝(Deep Copy)指递归复制数组的所有层级元素,包括嵌套的引用类型(如对象、数组),生成与原数组完全独立的新数组。修改深拷贝后的数组不会影响原数组,二者无任何内存地址共享
核心特点:
所有层级元素独立,包括基本类型和引用类型;
支持复杂数据结构(如多层嵌套、特殊对象类型)的完全隔离
实现方法
JSON.parse(JSON.stringify())
通过序列化与反序列化生成新数组,支持大部分数据类型。
适用场景 简单数据结构的深拷贝(无函数、undefined
)
局限性 忽略函数、undefined
、Symbol
,无法处理循环引用,不支持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, {}, {}]
完全独立副本:新数组与所有嵌套元素均无引用关系
数据序列化:通过 JSON 格式转换实现深拷贝
适用场景:适合处理 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 跟踪
类型识别机制:
优先处理
Date
和RegExp
等特殊对象自动区分数组和普通对象
保留原型链特性(通过
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,支持复杂类型(如Date
、Map
、Set
)和循环引用,性能较好。
适用场景 现代浏览器环境,需处理复杂类型或循环引用
限性 部分旧浏览器不支持,无法复制函数或原型链属性
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));
}
现代浏览器原生支持:Chrome 98+/Firefox 94+/Safari 15.4+ 等现代浏览器支持
完整类型支持:
✅ 处理循环引用
✅ 保留 Date/RegExp/Map/Set 等特殊类型
✅ 支持 ArrayBuffer/Blob 等二进制类型
深度克隆:所有层级数据均独立,无引用共享