shift() 适合取走队列头部的通知,因其符合fifo语义、简洁可读;但需防空数组、性能瓶颈及并发竞态,推荐链式驱动处理并妥善重试。

为什么 shift() 适合取走队列头部的通知
消息队列是典型的 FIFO(先进先出)结构,而 shift() 正是从数组开头移除并返回第一个元素——语义和行为完全匹配。它比 pop() 更准确,也比手动用 splice(0, 1) 更简洁、可读性更强。
但要注意:shift() 是原地操作,会修改原数组;且时间复杂度为 O(n),因为所有后续元素都要向前移动一位。如果队列常达数千条,频繁调用 shift() 可能成为性能瓶颈。
实际使用中必须检查数组是否为空
对空数组调用 shift() 不报错,但返回 undefined。若后续逻辑直接访问返回值的属性(比如 notification.id),就会触发 Cannot read property 'id' of undefined 错误。
推荐写法:
const next = notifications.shift();
if (next == null) {
return; // 队列已空,不继续发送
}
sendNotification(next);
- 用
== null同时覆盖null和undefined,比!next更安全(避免误判false、0、''等 falsy 值) - 不要在
if (notifications.length)判断后再shift()—— 多线程或异步回调下可能被其他代码抢先清空
与 unshift() 配合实现“插队”逻辑
某些场景需要高优先级通知立即发送(比如错误告警),这时不能只靠 push() 追加到末尾。可以用 unshift() 把它塞到队首,再由下一次 shift() 拿到。
示例:
// 普通通知入队
notifications.push({ type: 'info', content: '同步完成' });
// 紧急通知插队
notifications.unshift({ type: 'error', content: '网络中断', priority: 'high' });
-
unshift()同样是 O(n),频繁插队会拖慢整体性能 - 若插队需求多,建议改用
Array.prototype.concat()构造新数组,或换用Deque类库(如double-ended-queue)
在定时轮询中调用 shift() 的典型陷阱
常见模式是用 setInterval 每秒检查并发送一条通知,但容易忽略两个问题:
- 异步发送(如
fetch)未完成时,下一轮定时器又触发shift(),导致跳过某条通知 - 发送失败后没有将通知放回队首或重试队列,造成丢失
更稳妥的做法是:只在上一条发送完成(无论成功或失败)后再主动取下一条,而不是依赖固定间隔。例如:
function processQueue() {
const item = notifications.shift();
if (!item) return;
sendNotification(item)
.catch(err => console.warn('发送失败,暂存重试', err))
.finally(() => setTimeout(processQueue, 0)); // 下一轮等当前结束再启动
}
这里的关键不是“定时”,而是“链式驱动”——每处理完一条才触发下一条,避免竞态和遗漏。
真正难的从来不是调用 shift() 这一行代码,而是它背后那个被修改的数组是否还在被其他地方读写,以及失败后有没有人记得把它捞回来。










