手写防抖与节流 | 刷题打卡
分类:javascript
手写防抖与节流
闲时要有吃紧的心思,忙时要有悠闲的趣味
- 掘金团队号上线,助你 Offer 临门! 点击 查看详情
目录
-
前言
-
正文
- 一、防抖(debounce)
- 二、节流 (throttle)
-
总结
-
参考文档
前言
返回目录
面试必问,不说废话。
正文
一、防抖(debounce)
返回目录
防抖,顾名思义,防止抖动,以免把一次事件误认为多次,敲键盘就是一个每天都会接触到的防抖操作。
原理
- 某个任务你并不想它太过频繁触发,那么设置一个指定间隔的定时器来延迟执行;
- 每次进来的时候都清除原本的定时器,然后重新开始计时;
- 只有任务触发的间隔超过指定间隔的时候,任务才会执行。
特点
- 如果在指定间隔内(如 1000ms)再次触发任务,那么当前的计时取消,所有任务不会执行,重新开始计时。
- 如果在指定间隔内(如 1000ms)没有再次触发任务,那么就执行最后一次任务
也就是说,如果触发太过频繁,会导致一次响应都没有。只能等你最后一次触发结束指定间隔(如 1000ms)后,才能执行最后一次任务。
应用场景
- 登录、发短信等按钮避免用户点击太快,以致于发送了多次请求,需要防抖
- 调整浏览器窗口大小时,resize 次数过于频繁,造成计算过多,此时需要一次到位,就用到了防抖
- 文本编辑器实时保存,当无任何更改操作一秒后进行保存
代码实现
「防抖重在清零 clearTimeout(timer)」
/**
* 防抖
* @param {String} fn 回调方法
* @param {String} delay 缓冲时间
*/
function debounce(fn, delay) {
// 创建一个标记用来存放定时器
let timeout = null
return function () {
// 每次函数触发的时候,清空之前的定时器
clearTimeout(timeout)
// 创建一个新的 setTimeout
// 这样就能保证点击按钮后的 delay 时间间隔内
// 如果用户还点击了的话,就不会执行 fn 函数
timeout = setTimeout(() => {
// 使用apply修正 this 指向,执行传入函数
fn.apply(this, arguments)
}, delay)
}
}
二、节流 (throttle)
节流,顾名思义,控制水的流量。控制事件发生的频率,如控制为 1s 发生一次,甚至 1 分钟发生一次。与服务端(server)及网关(gateway)控制的限流 (Rate Limit) 类似。
原理
- 初始化一个开关锁为真
- 设置闭包函数,函数内判断标记为真才执行,然后将标记置为假
- 闭包函数内一个指定间隔的定时器来延迟执行任务,执行完则再将标记置真;
特点
- 无论在指定间隔内(如 1000ms)触发多少次任务,只执行第一次任务
- 在触发任务的指定间隔(如 1000ms)之后,肯定执行第一次任务。
也就是说,只要你触发一次任务,每在指定间隔(如 1000ms)之后肯定执行且只执行第一次;循环往复。
应用场景
- scroll 事件,每隔一秒计算一次位置信息等
- 浏览器播放事件,每个一秒计算一次进度信息等
- input 框实时搜索并发送请求展示下拉列表,每隔一秒发送一次请求 (也可做防抖)
代码实现
「节流重在开关锁」
/**
* 节流
* @param {String} fn 回调方法
* @param {String} delay 缓冲时间
*/
function throttle(fn, delay) {
// 初始化一个状态为真
let canRun = true
return function () {
// 判断状态,休息时间 暂不接客
if (!canRun) {
return
}
// 工作时间,执行函数;
// 在间隔期内把状态位设为假
canRun = false
// 创建定时器,延迟执行任务
setTimeout(() => {
// 使用apply修正 this 指向,执行传入函数
fn.apply(this, arguments)
// 执行完任务之后,重新将这个标志设置为真
canRun = true
}, delay)
}
}
总结
返回目录
开始手写系列。
路漫漫其修远兮,与诸君共勉。
参考文献
- jsliang 求职系列 - 12 - 手写防抖和节流 | 掘金-jsliang
- 浅谈 JS 防抖和节流 | 思否-安歌
附演示代码
<div>
<button id="debounceBtn">点我防抖</button>
<button id="throttleBtn">点我节流</button>
</div>
<script>
// 点击按钮次数
let clickNum = 0
// 按钮点击时间
let clickTime = 0
// 按钮开始点击时间
let startTime = 0
// 执行时间
let executionTime = 0
// 获取按钮
const debounceBtn = document.getElementById('debounceBtn')
const throttleBtn = document.getElementById('throttleBtn')
// 绑定点击事件
debounceBtn.onclick = function (e) {
clickBtn()
debounceFn(clickNum)
}
throttleBtn.onclick = function (e) {
clickBtn()
throttleFn(clickNum)
}
// 节流及防抖事件
let debounceFn = debounce(fnExecution, 1000)
let throttleFn = throttle(fnExecution, 1000)
// 点击按钮事件
function clickBtn() {
clickNum++
clickTime = Date.now()
if (clickNum === 1) {
startTime = Date.now()
}
console.log(`这是第${clickNum}次点击`)
}
function fnExecution(num) {
executionTime = Date.now()
console.log(
`生效的是第 ${num}次点击,生效时间与最后一次点击隔时间为${
executionTime - clickTime
}毫秒,生效时间与第一次点击隔时间为${executionTime - startTime}毫秒`
)
clickNum = 0
}
/**
* 防抖
* @param {String} fn 回调方法
* @param {String} delay 缓冲时间
*/
function debounce(fn, delay) {
// 创建一个标记用来存放定时器
let timeout = null
return function () {
// 每次函数触发的时候,清空之前的定时器
clearTimeout(timeout)
// 创建一个新的 setTimeout
// 这样就能保证点击按钮后的 delay 时间间隔内
// 如果用户还点击了的话,就不会执行 fn 函数
timeout = setTimeout(() => {
// 使用apply修正 this 指向,执行传入函数
fn.apply(this, arguments)
}, delay)
}
}
/**
* 节流
* @param {String} fn 回调方法
* @param {String} delay 缓冲时间
*/
function throttle(fn, delay) {
// 初始化一个状态为真
let canRun = true
return function () {
// 判断状态,休息时间 暂不接客
if (!canRun) {
return
}
// 工作时间,执行函数;
// 在间隔期内把状态位设为假
canRun = false
// 创建定时器,延迟执行任务
setTimeout(() => {
// 使用apply修正 this 指向,执行传入函数
fn.apply(this, arguments)
// 执行完任务之后,重新将这个标志设置为真
canRun = true
}, delay)
}
}
</script>
后记:Hello 小伙伴们,如果觉得本文还不错,记得点个赞或者给个 star,你们的赞和 star 是我编写更多更丰富文章的动力!GitHub 地址
文档协议
db 的文档库 由 db 采用 知识共享 署名-非商业性使用-相同方式共享 4.0 国际 许可协议进行许可。
基于github.com/danygitgit上的作品创作。
本许可协议授权之外的使用权限可以从 creativecommons.org/licenses/by… 处获得。