debounce防抖和throttle节流函数,在开发中经常会用到,今天就试着来手写一下吧~
当然也是面试题整理
手写debounce
参考underscore的函数文档,防抖函数主要实现的功能如下:
返回 function 函数的防反跳版本, 将延迟函数的执行(真正的执行)在函数最后一次调用时刻的 wait 毫秒之后. 对于必须在一些输入(多是一些用户操作)停止到达之后执行的行为有帮助。所以防抖函数一般会调用时刻 wait 结束之后执行,并且一般只执行一次。
防抖的应用场景可以是:当窗口停止改变大小之后重新计算布局,当滑动到页面底部之后再请求数据(实现懒加载),或者防止连续点击按钮的误触(当然这个也可以通过节流实现)
根据underscore的文档,debounce的参数包括function执行的回调函数, wait调用时刻, 以及布尔类型参数immediate,如果immediate为true,那么函数在调用后立即执行。此外,还可以调用.cancel()实现取消预定的debounce。
那么,让我们开始吧
const myDebounce = function (func, wait, immediate) {
immediate = immediate || false // immediate默认为false
let timerId = null // 初始化定时器
// 因为debounce返回的函数可调用,所以需要返回一个函数
return function () {
// 保留函数调用时的this,否则返回函数将指向window
const context = this
// 获得当前函数的传入参数
const args = arguments
// 保存函数运行后的结果
let result
// 如果存在定时器,需要将定时器清除
if(timerId){ clearTimeout(timerId) }
if(!immediate){
// 如果不是立即执行
timerId = setTimeout(() =>{
result = func.apply(context, args)
}, wait)
}else{
let callNow = !timerId
// 判断是否为初次执行,若timerId为null,为初次执行
timerId = setTimeout(() =>{
// 在wait结束时,将timerId置空,保证下次为初次执行状态
timerId = null
}, wait)
// 通过callNow 实现wait的时间间隔
if (callNow){ result = func.apply(context, args) }
}
return result
}
}
myDebounce.cancle = function () {
// 取消的实现,就是取消当前的timerId
clearTimeout(timerId)
timerId = null // 将闭包的变量置空,防止内存泄漏
}
手写throttle
节流throttle在underscore的函数文档中,有如下的定义:
创建并返回一个像节流阀一样的函数,当重复调用函数的时候,至少每隔wait毫秒调用一次该函数。对于想控制一些触发频率较高的事件有帮助。所以节流函数的实现方式和debounce还蛮类似的,先来一个时间戳版本
时间戳实现throttle
const myThrottle = function (func, wait) {
// 之前的时间戳
let lastTime = 0
return function fn () {
const context = this
const args = arguments
let result
const now = new Date().valueOf()
// 每次执行函数时都会比较调用时间now和上一次执行时间lastTime
if (now - lastTime > wait){
result = func.apply(context, args)
// 重置lastTime
lastTime = now
}
return result
}
}
时间戳实现方案,在函数初次调用时就会执行,之后间隔wait时段后,再次执行。
在throttle函数中,第三个传入参数options
默认情况下,throttle将在你调用的第一时间尽快执行这个function,并且,如果你在wait周期内调用任意次数的函数,都将尽快的被覆盖。如果你想禁用第一次首先执行的话,传递{leading: false},还有如果你想禁用最后一次执行的话,传递{trailing: false}。所以,我们的时间戳版本,实现的是`option`为默认值`{leading: true, trailing: false}`状态下的throttle函数
而setTimeout 则可以实现option为{leading: false, trailing: true}状态下的throttle函数
定时器实现throttle
const myThrottle = function (func, wait) {
let timerId = null
return function fn () {
const context = this
const args = arguments
let result
clearTimeout(timerId)
// 当timerId为空时,设置时长为wait的定时器,执行函数
if (!timerId) {
timerId = setTimeout(() => {
result = func.apply(context, args)
// 在函数执行后,将定时器置空,防止再次执行
timerId = null
}, wait)
}
return result
}
}
结合时间戳和定时器,我们似乎就可以得到,支持optin设置的完整版的throttle
完整版throttle
const myThrottle = function (func, wait, option) {
const leading = option.leading || true
const trailing = option.trailing || false
let timerId = null
let lastTime = 0
return function fn () {
const context = this
const args = arguments
let result
const now = new Date().valeOf()
// 若leading为false,则初始时间戳作废
if( !leading && !lastTime){
lastTime = new Date().valeOf()
}
if (now - lastTime > wait){
result = func.apply(context, args)
lastTime = now
// 确保timerId是清空的
if (timerId) {
clearTimeout(timerId)
timerId = null
}
} else if ( trailng && !timerId) {
// 当 trailing 为 true 时执行
clearTimeout(timerId)
timerId = setTimeout(() => {
result = func.apply(context, args)
timerId = null
// 更新时间戳
lastTime = new Date().valeOf()
}, wait)
}
return result
}
}
// 实现取消功能
myThrottle.cancle = function () {
clearTimeout(timerId)
timerId = null
lastTime = new Date().valueOf
}