前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
首页程序员面试题库数组头部尾部操作性能差异
JavaScript数组与集合

数组头部 shift、unshift 为什么常比尾部 push、pop 成本高?

push 和 pop 只动数组末尾,其他元素原地不动。shift 和 unshift 动的是开头,后面每个元素的索引都要加 1 或减 1,数组越长挪得越多。所以通常说尾部操作是 O(1),头部操作是 O(n)。

前端进阶之旅 · 一题精讲更新于 2026.09.29
JavaScript#数组与集合#性能优化#算法
先看核心答案读代码示例
理解线索

头尾操作的差别在于要不要挪索引

  1. push 和 pop只改最后一个位置和 length,前面的元素不动
  2. shift 和 unshift开头增删一个,后面所有元素都要换索引
  3. 队列游标用一个 head 变量记住队头,出队只是把它加 1

push 说是 O(1),指的是摊还成本。底层空间不够时引擎要扩容并搬一次数据,那一次会慢,但平均下来每次仍是常数时间。

核心回答

先记住这个答案

数组元素是按索引连续存放的。push 和 pop 只动末尾,前面的元素位置都不用变,通常是 O(1)。shift 删掉第一个元素后,剩下的每个元素索引都要减 1;unshift 在开头插入,每个元素索引都要加 1,所以是 O(n),数组越长越慢。用数组当队列时,如果每次出队都 shift,处理 n 个元素总共就是 O(n²)。数据量大时,可以用一个游标记住队头的位置,出队只移动游标。

  • 尾部增删通常是 O(1),头部增删是 O(n)
  • 加元素的返回新长度,删元素的返回被删元素
  • 大量出队不要反复 shift,用游标记录队头
  • 四个方法都会修改原数组

先把四个方法的返回值分清

性能之外,面试还常考这四个方法返回什么,很多人会记混。

头尾增删和返回值JavaScript
const head = ['B', 'C']
console.log(head.unshift('A'))     // 返回新长度
console.log(head.shift())          // 返回被移除的元素
console.log(JSON.stringify(head))

const tail = ['A', 'B']
console.log(tail.push('C'))        // 返回新长度
console.log(tail.pop())            // 返回被移除的元素
console.log(JSON.stringify(tail))
查看输出与解释
3
A
["B","C"]
3
C
["A","B"]

规律很好记:加元素的 push 和 unshift 返回新长度,删元素的 pop 和 shift 返回删掉的那个元素。四个方法都直接修改原数组,没有一个返回新数组,所以不能写成 arr.push(1).map(...)。

为什么头部操作更慢

可以把数组想成电影院里的一排座位,每个人坐在编了号的座位上。散场时最后一个人离开,其他人不用动,这是 pop。如果坐在 1 号位的人离开,还要求座位从 1 号开始连续坐满,后面的人就得每人往前挪一个座位,这就是 shift。人越多挪得越久。

用游标代替反复 shift
处理消息队列、做 BFS 广度优先遍历时,经常要不断从队头取元素。用一个 head 变量记住当前队头的索引,出队时读 queue[head] 再让 head 加 1,元素不用挪,每次出队都是 O(1)。 游标方案有一个代价:已经出队的元素还留在数组里,占着内存。长期运行的队列要定期清理,比如 head 超过数组一半长度时,用 queue = queue.slice(head) 丢掉前面的部分,再把 head 归零。这些都是在数据量真的大时才值得做的优化,几百个元素的数组直接 shift 完全没问题。
回答前,多想一步

容易答错的地方

说每次 push 都一定是 O(1)
数组底层会预留一些空间,空间用完时引擎要申请更大的空间并把数据搬过去,那一次 push 是 O(n)。因为扩容不是每次都发生,平均摊到每次操作上还是常数时间,所以准确的说法是摊还 O(1)。
以为引擎会把 shift 优化成 O(1)
有些引擎对特定情况做了优化,小数组上可能感觉不到差别,但语言规范没有这种保证。回答时说「按规范语义 shift 需要移动后续元素,通常按 O(n) 估算,实际性能要在目标环境里测」最稳妥。
试着用自己的话回答

面试官还会怎么问?

空数组调用 pop 或 shift 会报错吗?

不会报错,返回 undefined,长度还是 0。所以如果队列里本身可能存 undefined,不能只靠返回值判断队列是否为空,要看 length。

unshift(1, 2) 和先 unshift(1) 再 unshift(2) 结果一样吗?

不一样。一次传多个参数时,它们按传入顺序整体放到开头,结果是 [1, 2, ...];分两次调用时,后插入的在最前面,结果是 [2, 1, ...]。

需要两头都频繁增删时用什么?

可以实现一个双端队列,用环形缓冲区加头尾两个指针,两头增删都是 O(1)。前端业务里真需要这个的场景不多,先确认数组确实是性能瓶颈再动手。

从一道题,走向一组知识

把知识连起来

数组与集合

map、filter、sort 链式调用会创建几个数组?怎么减少中间分配?

链式调用 map、filter 会产生中间数组,和这里的索引搬动一样,都是数组操作里容易被忽略的开销。

数组与集合

遍历数组为什么写 i < length?怎么避免多循环一次或少循环一次?

用游标实现队列时要算清有效范围和空队列的条件,那篇讲循环边界怎么写不出错。

参考资料

  • MDN:shift
  • MDN:Array

示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。

本题目录
  1. 先记住这个答案
  2. 先把四个方法的返回值分清
  3. 为什么头部操作更慢
  4. 容易答错的地方
  5. 面试官还会怎么问
  6. 把知识连起来
读懂,再试着讲出来

先看核心答案,再读代码。最后展开追问,检查自己有没有遗漏边界。

试着回答追问
浏览全部面试题理解原理,也关注真实的使用场景。回到顶部 ↑