4. 技巧
2024/10/3大约 2 分钟
4. 技巧
4.1. 防抖和节流
防抖:单位时间内频繁触发事件,只执行最后一次
处理的思路在于使用定时器
- 声明一个可存储定时器的变量
- 在事件触发时,判断是否有定时器,有则清除定时器,再重新设置定时器;没有定时器则设置定时器
- 定时器里面执行事件处理逻辑
常用于输入验证和搜索建议
function debounce(fn, delay) { let timer = null return function () { if (timer) clearTimeout(timer) timer = setTimeout(() => { fn() }, delay) } }Lodash中有用于防抖的函数,可以使用_.debounce(fn, delay)调用
节流:在事件触发时,规定一个执行周期,规定时间内只执行一次事件处理逻辑,也可以实现前一个任务还在,则取消触发
- 常用于高频事件比如鼠标移动、页面滚动等
- 处理规定时间内只执行一次的思路在于使用定时器
- 声明一个可存储定时器的变量
- 如果事件触发,先判断是否有定时器,如果有不创建定时器;没有才创建定时器
- 定时器里面执行事件处理逻辑,同时在定时器结束的时候,将定时器置为
null
function throttle(fn, delay) { let timer = null return function () { if (timer) return timer = setTimeout(() => { try { fn() } finally { timer = null } }, delay) } }Lodash中有用于节流的函数,可以使用_.throttle(fn, delay)调用- 处理前一个任务还在,取消触发的思路也类似,只是定时器可以换成一个
flag变量,由于js本身没有锁的实现,可能出现多个线程同时访问锁变量,只适合不强制要求必须只执行一个任务的场景,可以考虑async-mutex
// 简单的实现,可能存在多个任务同时执行,只是在前端可能性极小 function runSkipIfBusy(fn) { let flag = true return function () { if (flag) { flag = false try { fn() } finally { // 防止错误抛出,导致任务无法再次执行 flag = true } } } } // 导入 async-mutex import * as asyncMutex from 'https://cdn.jsdelivr.net/npm/async-mutex@0.5.0/+esm' const mutex = new asyncMutex.Mutex(); async function runSkipIfBusy(fn) { if (mutex.isLocked()) { console.log("任务在执行中") return false; } await mutex.runExclusive(fn); return true; } async function doTask(id) { await runSkipIfBusy(async () => { console.log(`任务 ${id} 开始`); await new Promise(r => setTimeout(r, 2000)); console.log(`任务 ${id} 结束`); }); } doTask(1); doTask(2); doTask(3);如果只希望同一时间只有一个任务在运行,可以使用
promise串联队列实现function createMutex() { let chain = Promise.resolve(); // 保存最后一个任务 return async function runExclusive(fn) { const run = async () => await fn(); chain = chain.then(run, run); return chain; }; }
