应避免在高频场景对大数组使用 unshift 或 shift,因其时间复杂度为 O(n),10 万项时比 pop 慢 500 倍以上;二者均需整体位移元素并触发内存复制,小数组(<100)可接受,大数组须改用 push/pop + 反向逻辑或双端队列结构。

别在高频场景里对大数组用 unshift 或 shift —— 它们不是慢一点,而是随数据量线性恶化,10 万项时比 pop 慢 500 倍以上。
unshift 和 shift 到底做了什么
这两个方法都强制操作数组「开头」,但 JavaScript 数组底层是连续内存块(V8 中叫 Fast Elements),往头部插入或删除元素,意味着所有后续元素都要整体位移一位:
-
unshift(...items):把原数组每个元素索引 +items.length,再把新元素填进前几位,最后更新length -
shift():把原数组每个元素索引 −1,丢弃索引 0 的值,再更新length - 两者都触发完整内存复制,时间复杂度稳定为
O(n),和数组当前长度直接挂钩
什么时候能放心用,什么时候必须换方案
小数组(
- 实时消息队列中频繁
unshift新消息 +shift消费旧消息 - 滚动加载列表前端维护「最近 100 条」,每次新数据来就
unshift再splice(100) - 动画帧循环中对几百项数组反复
shift做 FIFO 调度
此时应切换为:push/pop 配合反向逻辑(如把「最新」存末尾),或引入 Deque 类(可用 npm install @datastructures-js/deque),其内部用双数组/链表实现,首尾操作均为 O(1)。
常见误用与错误现象
开发者常以为「语法简单 = 性能友好」,结果在压测时发现 CPU 突增、帧率骤降,却查不到瓶颈。典型表现包括:
- Chrome DevTools 的 Performance 面板中,
shift调用占单帧 JS 执行时间 >60% - 数组从 1k 增长到 10k 后,相关操作耗时不是翻 10 倍,而是翻 80–100 倍(因缓存失效+内存重分配)
- IE 或旧版 Edge 中
unshift返回undefined(而非新长度),导致逻辑错乱(虽已淘汰,但遗留系统仍存在)
验证方式很简单:
const arr = new Array(100000).fill(0);
console.time('shift');
for (let i = 0; i < 100; i++) arr.shift();
console.timeEnd('shift'); 对比 pop 版本,差距一目了然。
替代写法示例:用 push/pop 模拟队列头操作
如果业务本质是 FIFO(比如任务队列),但不想引入第三方库,可反转数据流向:
- 入队不用
unshift,改用push(把新任务加到末尾) - 出队不用
shift,改用pop(从末尾取最新入队的任务) - 若必须「先入先出」语义,就把整个数组逻辑倒过来理解:索引大的是老数据,索引小的是新数据 → 出队时
pop,入队时unshift仍不行,那就push后再reverse()一次(仅限极低频)
更稳妥的做法是封装一层:
class SimpleQueue {
constructor() { this._arr = []; }
enqueue(item) { this._arr.push(item); }
dequeue() { return this._arr.shift(); } // 这里仍是瓶颈点
} → 真正要替换的,是 dequeue 内部实现,而不是表面语法。
真正卡住性能的从来不是「会不会写 unshift」,而是没意识到它背后那场静默的内存搬迁。数组越长,每次调用越像在搬整栋楼的家具——而你只动了一张椅子。


















