欢迎光临
我们一直在努力

从零吃透 JS 数组:数据结构思维 + 底层原理 + 实战避坑指南

文章目录

  • 一、前端必须掌握的基础数据结构
  • 二、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 区别

  • 在js 中创建一个const arr = new Array(7) 生成:[empty × 7]
  • empty 是什么?

    • 不是 null
    • 不是 undefined
    • 是数组空槽,位置未被占用、未初始化

    三者终极区别

  • null:人为手动置空(空对象)
  • undefined:系统默认空(声明未赋值、属性不存在)
  • empty:数组空槽,ES 特有,无类型、无属性
  • const arr = new Array(7);
    console.log(arr[0]); // undefined
    console.log(arr.hasOwnProperty(0)); // false(关键!空槽不存在属性)

  • new Array(7).fill(1) 直接填充基本类型值 所有位置固定为 1,无坑。

  • 六、原型链延伸

    核心结论

  • Array 是函数,所有函数都由 Function 构造
  • 普通对象的 __proto__ 才指向 Object.prototype

  • 完整链路

    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;
    // 只有第一项改变,完全正常


    九、双层循环优化

    原始写法问题

  • 每次循环读取 length,重复寻址
  • 内层频繁读取 arr[i]
  • 多次 IO 打印性能差
  • 优化版(标准最优写法)

    // 缓存外层长度
    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
    • 缓存行数据,减少重复寻址
    • 性能显著提升

    全文总结&知识点复盘&避坑指南

    全文总结

  • 前端基础数据结构核心:数组、链表、栈、队列、树、二叉树
  • JS 数组是动态弱类型,和强类型语言完全不同
  • 增删 API 全部是非纯函数,会修改原数组
  • empty / null / undefined 三者本质完全不同
  • 函数原型链:Array → Function.prototype → Object.prototype
  • 二维数组 fill 引用类型会共享内存,必须循环初始化
  • 遍历各有优劣:追求性能用 for,追求语义用 for…of,批量处理用高阶函数

  • 核心知识点复盘

    • 纯函数:无副作用、不依赖外部、输出稳定
    • 非纯函数:push/pop/shift/unshift
    • 空槽 empty 不等于 undefined
    • 引用类型 fill 会导致数据联动修改
    • 数组遍历根据场景选择不同方案

    常见避坑指南

  • ❌ 不要用 fill([]) 创建二维数组
  • ❌ 不要混淆函数原型和对象原型
  • ❌ 不要依赖 empty 空槽做业务逻辑
  • ❌ 不要滥用 forEach 需要中断的场景
  • 赞(0)
    未经允许不得转载:171主机测评 » 从零吃透 JS 数组:数据结构思维 + 底层原理 + 实战避坑指南
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址