面试题:不使用setTimeout/clearTimeout实现自定义setInterval与clearInterval
复刻自定义setInterval和clearInterval(不使用setTimeout/clearTimeout)
嘿,这个需求挺有挑战性的——要不用原生定时器API来实现定时循环对吧?我想到可以利用浏览器的MessageChannel来构建一个异步消息循环,这样既能避免阻塞主线程,又能模拟出定时触发的效果。下面是完整的实现思路和代码:
核心思路
我们需要几个关键部分来支撑这个实现:
- 一个任务存储对象,用来追踪每个定时任务的回调、间隔时间、执行状态
- 唯一ID生成器,给每个任务分配独有的标识,方便后续取消
- 用
MessageChannel创建异步循环,替代原生定时器的触发机制
完整代码实现
// 存储所有活跃的定时任务,key为任务ID,value为任务详情 const intervalTasks = {}; // 生成唯一任务ID的计数器 let taskIdCounter = 0; // 创建MessageChannel用于驱动异步循环 const channel = new MessageChannel(); const { port1, port2 } = channel; // 标记消息循环是否在运行,避免重复启动 let isLoopRunning = false; // 消息循环的核心逻辑:每次收到消息时检查所有任务是否到了执行时间 port2.onmessage = () => { const currentTime = Date.now(); // 遍历所有任务,检查是否需要执行 Object.values(intervalTasks).forEach(task => { if (task.active && currentTime - task.lastExecTime >= task.delay) { // 执行回调函数 task.callback(); // 更新上一次执行的时间戳 task.lastExecTime = currentTime; } }); // 检查是否还有活跃任务,有则继续循环,无则停止 const hasActiveTasks = Object.values(intervalTasks).some(task => task.active); if (hasActiveTasks) { port1.postMessage(''); } else { isLoopRunning = false; } }; // 自定义setInterval实现 function mySetInterval(callback, delay) { // 生成唯一任务ID const taskId = ++taskIdCounter; // 将任务信息存入存储对象 intervalTasks[taskId] = { callback, delay, lastExecTime: Date.now(), active: true }; // 如果循环还未启动,启动它 if (!isLoopRunning) { isLoopRunning = true; port1.postMessage(''); } // 返回任务ID,用于后续取消 return taskId; } // 自定义clearInterval实现 function myClearInterval(taskId) { const task = intervalTasks[taskId]; if (task) { // 标记任务为非活跃,后续循环会忽略它 task.active = false; // 可选:直接删除任务释放内存 // delete intervalTasks[taskId]; } }
使用示例
// 启动一个每秒触发一次的定时任务 const myIntervalId = mySetInterval(() => { console.log(`自定义定时任务触发:${new Date().toLocaleTimeString()}`); }, 1000); // 5秒后取消这个任务(这里用原生setTimeout只是为了演示,实际场景可以按需调用myClearInterval) setTimeout(() => { myClearInterval(myIntervalId); console.log('自定义定时任务已取消'); }, 5000);
关键点说明
- 异步循环机制:
MessageChannel的消息传递是异步的,不会阻塞主线程,每次port1.postMessage都会触发port2.onmessage回调,形成循环。 - 时间精度控制:通过记录每个任务的上一次执行时间戳,对比当前时间来判断是否达到触发间隔,尽可能保证执行间隔接近设定值。
- 资源优化:当所有任务都被取消后,消息循环会自动停止,避免不必要的资源消耗。
内容的提问来源于stack exchange,提问作者guest
相关产品推荐
相关产品推荐

