JavaScript 数组全方位解析:从基础到高级 API
在 JavaScript 中,数组是处理批量数据的核心工具,支持任意类型存储、动态长度调整,且提供了丰富的 API 用于数据操作。本文在原有基础上,针对数组的创建细节、访问规则、遍历场景、API 参数差异补充更全面的代码案例,尤其聚焦易混淆的 API(如slice与splice、every与some),帮你彻底扫清数组使用盲区。
一、数组核心特性(补充说明)
数组本质是 “有序键值对集合”,键为索引(0 开始的整数),值可任意类型,核心特性补充两点:
- 动态长度的底层逻辑:当给超出当前长度的索引赋值时(如
arr[5] = 10),数组会自动将length设为索引+1,中间未赋值的索引(如 3、4)会填充为empty(空元素,非undefined)。 - 空元素与
undefined的区别:new Array(3)创建的是 3 个空元素(empty × 3),遍历(如forEach)会跳过;而[undefined, undefined]是明确的undefined值,遍历会正常处理。
二、数组创建(补充细节代码)
数组创建有两种方式,但不同参数会导致完全不同的结果,以下是易混淆场景的代码对比:
1. 字面量模式(补充嵌套数组创建)
javascript
// 1. 基础字面量(任意类型)
var baseArr = [1, 2, null, "", true, {}, [], function() { console.log("hello") }];
console.log("基础字面量数组:", baseArr);
// 输出:[1, 2, null, "", true, {}, [], ƒ]
// 2. 补充:嵌套数组(二维/多维数组)
var nestedArr = [
[1, 2], // 二维数组
[3, [4, 5]] // 三维数组
];
console.log("二维数组访问:", nestedArr[0][1]); // 2(访问第一行第二列)
console.log("三维数组访问:", nestedArr[1][1][0]); // 4(访问第二行第二列第一元素)
2. 构造函数模式(补充参数差异对比)
new Array()的参数数量和类型会直接影响数组创建结果,是高频踩坑点,代码对比如下:
javascript
// 1. 无参数:空数组
var emptyArr = new Array();
console.log("无参数构造:", emptyArr); // []
// 2. 单个数字参数:指定长度的空数组(元素为empty)
var lenArr = new Array(3);
console.log("单个数字参数(长度3):", lenArr); // [empty × 3]
console.log("空数组遍历(forEach跳过empty):");
lenArr.forEach(item => console.log(item)); // 无输出(跳过空元素)
// 3. 单个非数字参数:包含该元素的数组
var strArr = new Array("3");
console.log("单个字符串参数:", strArr); // ["3"]
// 4. 多个参数:包含所有参数的数组
var multiArr = new Array("3", "4", 5);
console.log("多个参数:", multiArr); // ["3", "4", 5]
// 5. 补充:警惕单个小数参数(报错)
try {
var errArr = new Array(2.4); // 无效数组长度
} catch (e) {
console.log("单个小数参数报错:", e.message); // "Invalid array length"
}
三、数组访问与遍历(补充核心场景)
1. 数组访问(补充索引边界与长度修改)
javascript
var arr = [10, 20, 30];
// 1. 正常访问(0 ~ length-1)
console.log("索引1元素:", arr[1]); // 20
// 2. 访问超出长度的索引(返回undefined)
console.log("索引10元素:", arr[10]); // undefined
// 3. 给超出长度的索引赋值(数组自动扩容)
arr[5] = 50;
console.log("扩容后数组:", arr); // [10, 20, 30, empty × 2, 50]
console.log("扩容后长度:", arr.length); // 6(索引5+1)
// 4. 手动修改length(截断/扩容)
arr.length = 4; // 截断数组,保留前4个元素
console.log("截断后数组:", arr); // [10, 20, 30, empty]
arr.length = 6; // 扩容数组,新增元素为empty
console.log("扩容后数组:", arr); // [10, 20, 30, empty, empty, empty]
2. 数组遍历(补充场景化代码)
不同遍历方式适用场景不同,以下是高频场景的代码实现:
javascript
var users = [
{ name: "tom", age: 18 },
{ name: "jerry", age: 16 },
{ name: "alice", age: 20 }
];
// 1. for循环(适合需要索引+中断遍历的场景)
console.log("for循环(找第一个成年人):");
for (var i = 0; i < users.length; i++) {
if (users[i].age >= 18) {
console.log("第一个成年人:", users[i]); // {name: "tom", age: 18}
break; // 找到后中断遍历
}
}
// 2. for...in循环(适合遍历非连续索引的数组)
var nonContinuousArr = [10, , 30, , 50]; // 含空元素的非连续数组
console.log("\nfor...in遍历非连续数组:");
for (var index in nonContinuousArr) {
console.log(`索引${index}: ${nonContinuousArr[index]}`);
}
// 输出:索引0:10 索引2:30 索引4:50(跳过空元素的索引1、3)
// 3. forEach(适合无中断的全量遍历)
console.log("\nforEach遍历(输出所有用户名):");
users.forEach(user => {
console.log("用户名:", user.name);
});
// 输出:tom、jerry、alice
// 4. while/do-while(适合动态条件遍历)
console.log("\ndo-while遍历(找年龄>17的用户):");
var j = 0;
do {
if (users[j].age > 17) {
console.log("年龄>17的用户:", users[j]);
}
j++;
} while (j < users.length);
// 输出:tom(18)、alice(20)
四、数组核心 API(补充完整代码案例)
针对原有大纲中易混淆、参数复杂的 API,补充详细代码示例,重点标注 “参数差异”“返回值”“是否修改原数组”:
1. 数组判断与转换(补充Array.isArray与类数组转换)
javascript
// 1. 判断是否为数组(typeof vs Array.isArray)
var arr = [1, 2];
var obj = { 0: 1, 1: 2 }; // 类数组对象
console.log("typeof arr:", typeof arr); // "object"(无法区分数组和对象)
console.log("Array.isArray(arr):", Array.isArray(arr)); // true
console.log("Array.isArray(obj):", Array.isArray(obj)); // false(类数组不是数组)
// 2. Array.from(类数组/字符串转数组)
// 场景1:类数组对象(如arguments、DOM集合)
var likeArr = { 0: "a", 1: "b", length: 2 }; // 类数组(需有length)
var arrFromLike = Array.from(likeArr);
console.log("类数组转数组:", arrFromLike); // ["a", "b"]
// 场景2:字符串转数组
var str = "BRIUP";
var arrFromStr = Array.from(str);
console.log("字符串转数组:", arrFromStr); // ["B", "R", "I", "U", "P"]
// 场景3:截取字符串(结合map)
var arrFromStrSlice = Array.from(str, (char, index) => {
if (index < 3) return char; // 截取前3个字符
});
console.log("截取字符串转数组:", arrFromStrSlice); // ["B", "R", "I", undefined, undefined]
// 3. Array.of(避免单个数字参数歧义)
console.log("Array.of(3):", Array.of(3)); // [3](元素为3)
console.log("new Array(3):", new Array(3)); // [empty × 3](长度为3的空数组)
console.log("Array.of(1, 2, 3):", Array.of(1, 2, 3)); // [1, 2, 3]
2. 栈 / 队列方法(补充多元素操作)
javascript
var arr = [1, 2];
// 1. push(添加多个元素)
var pushLen = arr.push(3, 4); // 一次添加2个元素
console.log("push后数组:", arr); // [1, 2, 3, 4]
console.log("push返回值(新长度):", pushLen); // 4
// 2. pop(删除末尾元素,无参数)
var popElem = arr.pop();
console.log("pop删除的元素:", popElem); // 4
console.log("pop后数组:", arr); // [1, 2, 3]
// 3. unshift(添加多个元素到头部)
var unshiftLen = arr.unshift(0, -1); // 一次添加2个元素
console.log("unshift后数组:", arr); // [-1, 0, 1, 2, 3]
console.log("unshift返回值(新长度):", unshiftLen); // 5
// 4. shift(删除头部元素,无参数)
var shiftElem = arr.shift();
console.log("shift删除的元素:", shiftElem); // -1
console.log("shift后数组:", arr); // [0, 1, 2, 3]
3. 排序方法(补充数字 / 字符串排序细节)
javascript
// 1. reverse(颠倒顺序,无参数)
var arr1 = [1, 2, 3];
arr1.reverse();
console.log("reverse后数组:", arr1); // [3, 2, 1]
// 2. sort(默认字典序 vs 数字排序)
// 场景1:默认字典序(字符串、数字都会按ASCII排序)
var mixArr = [10, 5, "2", "100"];
mixArr.sort();
console.log("默认字典序排序:", mixArr); // ["100", "2", 5, 10](按首字符ASCII排序)
// 场景2:数字升序(传比较函数 (a,b) => a - b)
var numArr = [40, 100, 1, 5];
numArr.sort((a, b) => a - b);
console.log("数字升序排序:", numArr); // [1, 5, 40, 100]
// 场景3:数字降序(传比较函数 (a,b) => b - a)
numArr.sort((a, b) => b - a);
console.log("数字降序排序:", numArr); // [100, 40, 5, 1]
// 场景4:对象排序(按对象属性排序)
var userArr = [
{ name: "tom", age: 18 },
{ name: "alice", age: 20 },
{ name: "jerry", age: 16 }
];
userArr.sort((a, b) => a.age - b.age); // 按age升序
console.log("按age排序后的对象数组:", userArr);
// 输出:[{name: "jerry", age:16}, {name: "tom", age:18}, {name: "alice", age:20}]
4. 操作方法(补充slice与splice对比)
slice(截取,不修改原数组)和splice(添加 / 删除,修改原数组)是最易混淆的 API,代码对比如下:
javascript
var arr = [1, 2, 3, 4, 5];
// 1. slice(start, end):截取,不修改原数组
// 场景1:正常截取(start=1,end=3,含start不含end)
var slice1 = arr.slice(1, 3);
console.log("slice(1,3)结果:", slice1); // [2, 3]
console.log("slice后原数组:", arr); // [1, 2, 3, 4, 5](未修改)
// 场景2:负索引(-1表示最后一个元素)
var slice2 = arr.slice(-2, -1); // 从倒数第2个到倒数第1个(不含)
console.log("slice(-2,-1)结果:", slice2); // [4]
// 场景3:深拷贝(无参数)
var copyArr = arr.slice(); // 等同于slice(0)
copyArr[0] = 100;
console.log("拷贝数组修改后:", copyArr); // [100, 2, 3, 4, 5]
console.log("原数组:", arr); // [1, 2, 3, 4, 5](未修改)
// 2. splice(index, num, ...item):添加/删除,修改原数组
// 场景1:删除元素(index=1,num=2:从索引1删除2个元素)
var deleteElem = arr.splice(1, 2);
console.log("splice删除的元素:", deleteElem); // [2, 3]
console.log("splice删除后原数组:", arr); // [1, 4, 5](已修改)
// 场景2:添加元素(index=1,num=0:在索引1前添加元素)
arr.splice(1, 0, 2, 3); // 添加2、3到索引1前
console.log("splice添加后原数组:", arr); // [1, 2, 3, 4, 5](已修改)
// 场景3:替换元素(index=2,num=1:删除1个元素,添加新元素)
arr.splice(2, 1, "a"); // 替换索引2的元素为"a"
console.log("splice替换后原数组:", arr); // [1, 2, "a", 4, 5](已修改)
5. 位置方法(补充重复元素查询)
javascript
var arr = [1, 2, 3, 2, 1];
// 1. indexOf(从前往后找)
// 场景1:找第一个2的索引
var firstIndex = arr.indexOf(2);
console.log("第一个2的索引:", firstIndex); // 1
// 场景2:从指定索引开始找(start=2)
var indexFrom2 = arr.indexOf(2, 2); // 从索引2开始找2
console.log("从索引2开始找2的索引:", indexFrom2); // 3
// 场景3:未找到返回-1
var notFound = arr.indexOf(4);
console.log("找4的索引(未找到):", notFound); // -1
// 2. lastIndexOf(从后往前找)
// 场景1:找最后一个1的索引
var lastIndex = arr.lastIndexOf(1);
console.log("最后一个1的索引:", lastIndex); // 4
// 场景2:从指定索引开始倒着找(start=3)
var lastIndexFrom3 = arr.lastIndexOf(1, 3); // 从索引3开始倒着找1
console.log("从索引3倒着找1的索引:", lastIndexFrom3); // 0
// 3. 补充:统计元素出现次数
function countElem(arr, elem) {
var count = 0;
var index = arr.indexOf(elem);
while (index !== -1) {
count++;
index = arr.indexOf(elem, index + 1); // 从下一个索引继续找
}
return count;
}
console.log("元素1出现的次数:", countElem(arr, 1)); // 2
6. 迭代方法(补充every/some/filter/map对比)
这 4 个方法均不修改原数组,但返回值和逻辑差异极大,代码对比如下:
javascript
var nums = [10, 20, 30, 40];
// 1. every(所有元素满足条件才返回true,空数组返回true)
var allGt15 = nums.every(num => num > 15);
console.log("是否所有元素>15:", allGt15); // false(10不满足)
console.log("空数组every结果:", [].every(() => false)); // true(空数组特殊处理)
// 2. some(任一元素满足条件返回true,空数组返回false)
var hasGt35 = nums.some(num => num > 35);
console.log("是否有元素>35:", hasGt35); // true(40满足)
console.log("空数组some结果:", [].some(() => true)); // false(空数组特殊处理)
// 3. filter(返回满足条件的元素数组)
var gt25 = nums.filter(num => num > 25);
console.log("元素>25的数组:", gt25); // [30, 40]
console.log("无满足条件时:", nums.filter(num => num > 40)); // [](空数组)
// 4. map(返回处理后的新数组,长度与原数组一致)
var doubleNums = nums.map(num => num * 2); // 每个元素乘2
console.log("元素乘2后的数组:", doubleNums); // [20, 40, 60, 80]
var addIndex = nums.map((num, index) => num + index); // 元素+索引
console.log("元素+索引后的数组:", addIndex); // [10, 21, 32, 43]
五、数组使用避坑指南(补充版)
-
空元素的遍历问题:
new Array(3)的空元素会被forEach、map跳过,但for循环会正常遍历(返回undefined),示例:javascript
var emptyArr = new Array(3); emptyArr.forEach(item => console.log(item)); // 无输出 for (var i = 0; i < emptyArr.length; i++) { console.log(emptyArr[i]); // undefined × 3 } -
slice的负索引边界:当start为负数且绝对值超过数组长度时,返回空数组,示例:javascript
var arr = [1, 2, 3]; console.log(arr.slice(-5, -1)); // [](start=-5超出数组长度3) -
splice的索引容错:当index为负数时,会从数组末尾开始计算(index=-1表示最后一个元素),示例:javascript
var arr = [1, 2, 3, 4]; arr.splice(-2, 1); // 从倒数第2个元素开始,删除1个 console.log(arr); // [1, 2, 4] -
sort的稳定性:ES2019 后sort是稳定排序(相同值的相对位置不变),示例:javascript
var users = [ { name: "a", age: 20 }, { name: "b", age: 18 }, { name: "c", age: 20 } ]; users.sort((a, b) => a.age - b.age); // 按age升序 console.log(users); // 输出:[{name:"b",age:18}, {name:"a",age:20}, {name:"c",age:20}](a和c的相对位置不变)
总结
数组是 JavaScript 中最灵活的数据结构之一,掌握其创建细节、访问规则、API 差异是高效开发的基础。本文补充了大量易混淆场景的代码案例,尤其聚焦 “参数影响结果”“是否修改原数组”“特殊边界处理” 三大核心点。建议收藏本文,在实际开发中根据场景选择 API:
- 需修改原数组:
push/pop/splice/sort/reverse - 不需修改原数组:
slice/concat/filter/map/every/some - 数组判断:优先用
Array.isArray,避免typeof - 类数组转数组:用
Array.from,比slice更灵活
更多推荐



所有评论(0)