文章目录
- 一、前端必须掌握的基础数据结构
- 二、JS 数组深度解析
-
- 2.1 JS 数组最大特点
- 2.2 数组底层内存原理
- 2.3 ADT 抽象数据类型
- 三、数组常用 API
- 四、纯函数 & 非纯函数
- 五、new Array() 深坑:empty、null、undefined 区别
- 六、原型链延伸
- 七、数组遍历全方案对比
-
- 1. for 循环(计数循环)
- 2. for…of
- 3. forEach
- 4. 高阶遍历(map / filter / every / some)
- 八、二维数组大坑
-
- 错误写法
- 正确创建二维数组
- 九、双层循环优化
-
- 原始写法问题
- 优化版(标准最优写法)
- 全文总结&知识点复盘&避坑指南
对于前端开发者来说,数组是一切数据结构的起点。
很多同学工作几年,只会 for 循环、push、map,但始终搞不懂:
- new Array(7) 产生的 empty 是什么?
- fill([]) 为什么所有数据联动修改?
- 为什么数组属于非纯函数?
这些看似简单的问题,正是面试高频、写代码容易翻车的关键点。
今天我们从零梳理,完全贴合 JS 视角,讲透数组底层、用法、遍历、二维数组深坑。
一、前端必须掌握的基础数据结构
所有算法、框架底层、业务复杂逻辑,全部基于这 6 种结构:
1. 列表(线性结构)
- 数组 各大语言原生内置,开箱即用,连续内存、查询最快。
- 链表 非连续存储,靠指针连接,增删快、查询慢。
- 栈 先进后出:函数调用栈、递归、编辑器撤销。
- 队列 先进先出:事件循环、异步队列、任务排队。
2. 树形(非线性结构)
- 树 层级结构:DOM树、组件树、菜单树。
- 二叉树 算法面试之王:遍历、递归、动态规划高频考点。
二、JS 数组深度解析
2.1 JS 数组最大特点
2.2 数组底层内存原理
数组是连续内存结构 寻址公式: 对应位置 = 起始地址 + 索引 × 偏移量
所以:
- 访问速度极快:O(1)
- 中间插入删除慢:需要平移元素:O(n)
2.3 ADT 抽象数据类型
数组的完整定义: 一段连续的存储空间 + 一套固定操作(增删改查)
不是简单的“一堆数据”,是完整的数据结构模型。
三、数组常用 API
以下四个方法全部修改原数组 → 非纯函数
| push | 尾部插入 | 新数组长度 |
| pop | 尾部删除 | 被删掉的元素 |
| unshift | 头部插入 | 新数组长度 |
| shift | 头部删除 | 被删掉的元素 |
示例代码
const arr = [1,2,3];
console.log(arr.push(4)); // 4
console.log(arr.pop()); // 4
console.log(arr.unshift(0)); // 4
console.log(arr.shift()); // 0
四、纯函数 & 非纯函数
纯函数特点
// 纯函数
function add(a,b){
return a+b;
}
非纯函数
let num = 0;
function add(b){
num += b;
return num;
}
依赖外部、修改外部、结果不可预测。
重点结论 push / pop / shift / unshift 都是非纯函数 因为它们会修改原数组(副作用)。
五、new Array() 深坑:empty、null、undefined 区别
empty 是什么?
- 不是 null
- 不是 undefined
- 是数组空槽,位置未被占用、未初始化
三者终极区别
const arr = new Array(7);
console.log(arr[0]); // undefined
console.log(arr.hasOwnProperty(0)); // false(关键!空槽不存在属性)
六、原型链延伸
核心结论
完整链路
Array.__proto__ === Function.prototype
Function.prototype.__proto__ === Object.prototype
Object.prototype.__proto__ === null
所以:
- Array.__proto__ !== Object.prototype
- Array.__proto__.__proto__ === Object.prototype
不要混淆:函数原型 和 对象原型!
七、数组遍历全方案对比
1. for 循环(计数循环)
优点:性能最强、可 break、可 continue 缺点:可读性差、命令式写法
2. for…of
优点:语义清晰、支持中断 最均衡推荐的遍历方式
3. forEach
缺点:无法 break,入调用栈,有上下文开销 参数:(item,index,self)
4. 高阶遍历(map / filter / every / some)
这些函数都是基于 forEach 展开的 全部为纯函数:返回新数组,不修改原数组
| map | 映射,一一对应,返回新数组 | 新数组 |
| filter | 筛选,保留回调为 true 的项 | 新数组 |
| every | 全部满足条件返回 true | Boolean |
| some | 任意一项满足返回 true | Boolean |
| reduce | 累加、聚合、复杂计算 | 任意类型(取决于回调) |
reduce 的参数和上面的有些不一样: (prev,item,index) 第一个参数是该索引的前一个元素
示例代码
const arr = [6,8,12,15];
console.log(arr.map(item => item*2));
console.log(arr.filter(item => item%2===0));// 筛选偶数
console.log(arr.every(item => item%2===0)); // false
console.log(arr.some(item => item%2===0)); // true
console.log(arr.reduce((sum,val)=> sum+val, 0));
八、二维数组大坑
错误写法
const arr = new Array(7).fill([]);
arr[0][0] = 1;
// 所有子数组全部被修改!
原因解释:
- fill(引用类型) 填充的是同一个内存地址
- 7 个子数组 全是同一个数组引用
- 修改一个,全部联动
正确创建二维数组
const arr = new Array(7);
for(let i = 0; i < arr.length; i++){
arr[i] = []; // 每次创建新数组
}
arr[0][0] = 1;
// 只有第一项改变,完全正常
九、双层循环优化
原始写法问题
优化版(标准最优写法)
// 缓存外层长度
const outerLen = arr.length;
for(let i = 0; i < outerLen; i++){
// 缓存当前行,减少寻址
const row = arr[i];
const innerLen = row.length;
for(let j = 0; j < innerLen; j++){
console.log(row[i], i, j);
}
}
优化点:
- 只读取一次 length
- 缓存行数据,减少重复寻址
- 性能显著提升
全文总结&知识点复盘&避坑指南
全文总结
核心知识点复盘
- 纯函数:无副作用、不依赖外部、输出稳定
- 非纯函数:push/pop/shift/unshift
- 空槽 empty 不等于 undefined
- 引用类型 fill 会导致数据联动修改
- 数组遍历根据场景选择不同方案
常见避坑指南



