文章目录
-
- websocket
- 定时轮询(setInterval)
- [惰性轮询(setTimeout 递归)](#惰性轮询(setTimeout 递归))
- [Web Worker 轮询](#Web Worker 轮询)
-
-
- [为什么要用 Web Worker 做轮询?](#为什么要用 Web Worker 做轮询?)
- [vue2 写法](#vue2 写法)
- [Vue3 + Vite 写法(最常用)](#Vue3 + Vite 写法(最常用))
- 使用场景
-
- [Periodic Background Sync](#Periodic Background Sync)
- requestIdleCallback
- SharedWorker

websocket
- 一次握手 → 永久保持连接(直到主动关闭)
- 双向通信:客户端 ↔ 服务器 随时互发消息
- 服务器有新数据 → 立刻推给前端
- 真正实时刷新数据
js
// 连接 WebSocket
const ws = new WebSocket('ws://localhost:8080/ws');
// 收到服务端推送的新数据,自动刷新页面
ws.onmessage = (e) => {
const newData = JSON.parse(e.data);
renderPage(newData);
};
// 发生错误/关闭
ws.onerror = () => {};
ws.onclose = () => {};
定时轮询(setInterval)
定时轮询是前端实现自动刷新数据最基础、最广为人知的方案。它通过 setInterval 定时器,每隔一段时间就向服务器发送一次请求,从而实现页面数据的自动更新。
js
// 1. 定义数据请求函数
async function fetchData() {
try {
const res = await fetch('/api/your-data-endpoint');
const data = await res.json();
updateUI(data); // 更新页面
} catch (err) {
console.error('请求数据失败:', err);
}
}
// 2. 设置定时器,每 3 秒(3000ms)请求一次
const intervalId = setInterval(fetchData, 3000);
// 3. 定义更新页面的方法(根据你的框架或原生DOM操作)
function updateUI(newData) {
// 例如 Vue: this.list = newData;
// 例如 原生: document.getElementById('data-container').innerText = newData.value;
console.log('数据已更新:', newData);
}
页面离开或不再需要轮询时,必须清除定时器,否则会造成内存泄漏。
js
// 清除定时器的两种常见时机
// 方式1:在某个事件中清除(如点击按钮)
function stopPolling() {
clearInterval(intervalId);
console.log('已停止轮询');
}
// 方式2:在页面卸载时清除(推荐)
window.addEventListener('beforeunload', () => {
clearInterval(intervalId);
});
惰性轮询(setTimeout 递归)
setInterval 有一个致命缺点:如果接口请求耗时超过了定时时间,会导致多个请求堆积,阻塞主线程或造成服务器压力。
惰性轮询(递归 setTimeout)能完美解决这个问题。它的规则是:等上一次请求完成(成功或失败)后,再延迟指定时间,发起下一次请求。
js
let timerId = null;
// 定义轮询函数
async function polling() {
try {
const res = await fetch('/api/your-data-endpoint');
const data = await res.json();
updateUI(data);
} catch (err) {
console.error('请求失败:', err);
} finally {
// 关键:无论成功失败,3秒后再执行下一次
timerId = setTimeout(polling, 3000);
}
}
// 启动轮询
polling();
// 停止轮询
function stopPolling() {
clearTimeout(timerId);
}
优缺点
| 特性 | setInterval (定时轮询) | setTimeout (惰性轮询) |
|---|---|---|
| 执行逻辑 | 固定时间间隔执行,不受请求耗时影响 | 上一次完成后,延迟固定时间再执行 |
| 请求堆积风险 | 高(请求慢时会堆积) | 低(串行执行,安全) |
| 适用场景 | 短请求、对时序要求不高的简单场景 | 绝大多数业务场景(推荐) |
| 代码复杂度 | 简单 | 稍复杂(需使用 finally) |
Web Worker 轮询
Web Worker 最大作用:开一个独立后台线程,不受主线程阻塞、页面切后台也不会被浏览器严重节流,用来做轮询非常稳,是普通项目里最实用的常驻刷新方案。
为什么要用 Web Worker 做轮询?
普通 setInterval 缺点:
- 页面切后台 → 浏览器会节流 / 变慢 / 暂停定时器
- JS 执行卡顿、渲染阻塞 → 定时器不准
- 大量计算时,轮询直接 "卡住不执行"
Web Worker 优点:
- 独立线程,不阻塞主线程
- 切后台、页面隐藏依然相对稳定执行
- 不会被 DOM 渲染、JS 阻塞影响
- 兼容性极好(IE10+、所有现代浏览器都支持
vue2 写法
安装依赖
npm install worker-loader -D
vue.config.js 配置
module.exports = {
configureWebpack: {
module: {
rules: [
{
test: /\.worker\.js$/,
use: { loader: 'worker-loader' }
}
]
}
}
}
创建 src/utils/poll.worker.js
js
// 后台轮询线程
let timer = null
// 接收主线程消息
self.onmessage = (e) => {
const { type, interval } = e.data
// 开始轮询
if (type === 'start') {
clearInterval(timer)
timer = setInterval(async () => {
try {
// 请求接口
const res = await fetch('/api/notice')
const data = await res.json()
// 发给 Vue 页面
self.postMessage({
status: 'success',
data
})
} catch (err) {
self.postMessage({ status: 'error', msg: err.message })
}
}, interval)
}
// 停止
if (type === 'stop') {
clearInterval(timer)
}
}
页面使用(Vue2 示例)
js
import PollWorker from '@/utils/poll.worker.js'
export default {
mounted() {
this.worker = new PollWorker()
this.worker.onmessage = (e) => {
console.log('新数据:', e.data.data)
// this.list = e.data.data
}
this.worker.postMessage({
type: 'start',
interval: 3000
})
},
beforeDestroy() {
this.worker.postMessage({ type: 'stop' })
this.worker.terminate()
}
}
Vue3 + Vite 写法(最常用)
Vite 内置支持 Web Worker,超级简单。
js
<script setup>
import { onMounted, onUnmounted } from 'vue'
// 直接引入 Worker(Vite 语法)
import PollWorker from '@/utils/poll.worker?worker'
let worker = null
onMounted(() => {
// 1. 创建 Worker
worker = new PollWorker()
// 2. 监听后台返回的新数据
worker.onmessage = (e) => {
if (e.data.status === 'success') {
console.log('后台刷新数据:', e.data.data)
// 这里更新 Vue 数据 → 页面自动刷新
// list.value = e.data.data
}
}
// 3. 启动轮询:3 秒一次
worker.postMessage({
type: 'start',
interval: 3000
})
})
// 页面销毁时关闭 Worker
onUnmounted(() => {
if (worker) {
worker.postMessage({ type: 'stop' })
worker.terminate() // 销毁线程
}
})
</script>
使用场景
页面切到后台 / 最小化,你依然希望轮询稳定执行
- 比如后台管理系统、监控页面、客服系统
- 用户切走窗口、最小化,普通 setInterval 会被浏览器节流、变慢、甚至暂停
- Worker 不会被轻易暂停,能保持基本定时精度
页面本身很卡、JS 执行重,定时器不准
- 大数据表格渲染、图表、大量 DOM 操作
- 主线程一卡,定时器就 "跳秒"
- Worker 是独立线程,不受主线程卡顿影响
worker 在使用结束后必须销毁,否则会导致内存泄露问题
Periodic Background Sync
它是浏览器级别的定时任务调度器,基于 Service Worker 运行,不受页面生命周期影响,专门用于后台定时同步数据。
Periodic Background Sync(周期性后台同步) 是专为 PWA 设计的、能在页面完全关闭后仍在后台定时执行网络任务的浏览器 API,完美解决你之前担心的 Worker 销毁、后台轮询失效问题。
核心机制
- 注册:在 Service Worker 注册时,指定唯一标签(tag)和最小间隔(minInterval)。
- 调度:浏览器内核接管计时,在设备联网、充电、闲置等低干扰时机触发。
- 执行:唤醒 Service Worker,触发 periodicsync 事件,执行同步逻辑。
- 持久化:注册后跨会话生效,直到主动取消。
代码示例
注册(主线程)
js
// 等待 Service Worker 就绪
navigator.serviceWorker.ready.then(async (registration) => {
// 检查支持
if (!registration.periodicSync) return;
try {
// 注册:每 4 小时同步一次(最小间隔)
await registration.periodicSync.register('order-sync', {
minInterval: 4 * 60 * 60 * 1000 // 14400000ms
});
console.log('周期性同步注册成功');
} catch (err) {
console.error('注册失败(权限/浏览器限制)', err);
}
});
监听与执行(Service Worker)
js
// sw.js
self.addEventListener('periodicsync', (event) => {
if (event.tag === 'order-sync') {
// 必须用 waitUntil 保证任务完成
event.waitUntil(
fetch('/api/order/sync')
.then(res => res.json())
.then(data => {
// 缓存新数据、更新 IndexedDB 等
return caches.open('order-cache').then(cache => {
return cache.put('/api/order/latest', new Response(JSON.stringify(data)));
});
})
.catch(err => console.error('同步失败', err))
);
}
});
- 浏览器支持:Chrome、Edge 支持;Firefox、Safari 暂不支持。
- 权限要求:需用户授予 "后台同步" 权限。
- 触发不保证:minInterval 是下限,浏览器会根据用户活跃度、电量、网络等策略调整,低活跃应用可能很久不触发。
- 网络限制:仅在已连接过的 Wi-Fi / 蜂窝网络下触发,陌生网络不执行。
- 任务时长:Service Worker 有超时限制(通常几分钟),长任务需拆分。
requestIdleCallback
requestIdleCallback(简称 rIC)是浏览器提供的主线程闲时调度 API,专门用来执行非紧急、非阻塞的后台任务,避免长任务卡住渲染与交互。
把不重要的任务见缝插针地放在浏览器空闲时段执行,优先保障渲染、动画、用户输入的流畅度。
二、核心原理
浏览器每帧(约 16ms)的工作:
- JS 执行 → 样式计算 → 布局 → 绘制 → 合成
- 如果一帧提前完成(比如只用了 10ms),剩余时间就是空闲时段
- requestIdleCallback 回调就在这个时段执行
回调会收到一个 deadline 对象:
- deadline.timeRemaining():当前空闲周期还剩多少毫秒(动态)
- deadline.didTimeout:是否因超时被强制执行
js
// 注册一个空闲回调
const handle = requestIdleCallback((deadline) => {
// 只要还有空闲时间,就处理任务
while (deadline.timeRemaining() > 0 && taskQueue.length > 0) {
const task = taskQueue.shift();
doHeavyWork(task); // 单次任务要轻
}
// 没做完,下次空闲继续
if (taskQueue.length > 0) {
requestIdleCallback(handle);
}
}, {
timeout: 2000 // 可选:2秒内没空闲就强制执行
});
// 取消
// cancelIdleCallback(handle);
SharedWorker
SharedWorker = 可以被多个标签页 / 多个窗口共享的同一个后台线程
这是它和普通 WebWorker 最核心的区别。
优点:
- 全局轮询、多页同步、避免重复请求
- 多页面共享同一个
- 最后一个页面关闭 → 才销毁
- 基于 port 通信
代码示例 :
共享线程文件:shared.worker.js
js
let timer = null;
let ports = [];
// 连接
self.onconnect = (e) => {
const port = e.ports[0];
ports.push(port);
// 监听页面消息
port.onmessage = (e) => {
if (e.data === 'start') {
startPoll();
}
};
port.start();
};
// 全局唯一轮询
function startPoll() {
if (timer) return;
timer = setInterval(async () => {
const res = await fetch('/api/notice');
const data = await res.json();
// 发给所有页面
ports.forEach(port => {
port.postMessage(data);
});
}, 3000);
}
页面中使用(任意页面都一样)
js
const worker = new SharedWorker('/shared.worker.js');
const port = worker.port;
// 开启
port.start();
// 接收共享 Worker 发来的数据
port.onmessage = (e) => {
console.log('新消息:', e.data);
};
// 启动轮询
port.postMessage('start');