防抖 & 节流

开始

防抖节流是前端性能优化的一种手段之一,它们的目的都是防止某一事件一段时间内频发触发,但是两者的原理不一样。
在这里插入图片描述
根据上图,我们也可以很清晰地看到他们的原理,总结如下:

  • 防抖:事件触发 n 秒后执行它的回调函数,如果 n 秒内重新触发,那么重新计时。
  • 节流: n 秒内只执行一次事件

防抖

  • 基于上述的定义,我们可以很快地写出第一版的代码
const debounce = function (fn, delay = 3000) {
    let timer = null // 闭包引用
    return function () {
        const args = arguments // 拷贝回调函数参数
        if (timer) {
            clearInterval(timer) // 清除定时器
            timer = null
        }
        // 每次都重新计时
        timer = setTimeout(() => {// 箭头函数解决 this 指向问题,不然指向的是 window 或者 global
            return fn.apply(this, args)
        }, delay)
    }
}
  • 很多场景下,上面这一版本已经够用了。比如说文本框的 change 监听,或者是 window.resize 的事件监听,我们关注的只是最后一次触发。

但难免有时候会有些特殊的需求,比如文本框第一次触发就想去请求后端数据,然后再进行后面的防抖过程,基于此我们又有了第二版的代码。

var debounce = function (fn, delay = 3000, immediate = false) {
    let timer = null

    return function () {
        const args = arguments, context = this

        if (timer) {
            clearTimeout(timer)
        }

        if (immediate) { // 要么是立即执行
            let isCall = !timer // 判断当前是否能执行
            timer = setTimeout(() => {
                timer = null
            }, delay)
            isCall && fn.apply(context, args)
        } else { // 要么就是正常流程
            timer = setTimeout(() => {
                fn.apply(context, args)
            }, delay)
        }

    }
}

节流

根据节流的定义,我们可以使用两种方法来实现,第一种是时间戳,第二种是定时器

// 基于时间戳的版本的头调用
const throttle = function (fn, delay) {
    let previous = 0

    return function () {
        let now = +new Date() // 转换为时间戳
        if (now - previous > delay) {// 如果满足当前时间- 上一次完成的时间 > 等待时间
            fn(arguments)
            previous = +new Date()
        }
    }
}

// 基于定时器实现的尾调用
const throttle = function (fn, delay) {
    let timer = null

    return function () {
        const context = this, args = arguments
        if (timer) { return } // 如果当前有定时任务,跳过
        timer = setTimeout(() => {
            fn.apply(context, args)
            timer = null
        }, delay)
    }
}
  • 时间戳的版本
    第一次事件触发就能运行(previous 为 0),但是如果中途停止触发的话那就退出了
  • 定时器版本
    等待 n 秒后第一次触发,如果中途退出的话仍然会执行最后一次(此时的timer 还在,定时任务还在)
  • 对比了两个版本,我们发现各有各的特点,那么有一种想法能不能把这两种特点联合起来呢,基于此就可以迭代出第三版了。
/*
	难点在于当定时器和时间戳两者发生冲突的时候选择哪一个?
	@example 
	{	
		delay = 1s 
		0s 时间戳立即执行,定时器同时设置 1s 回调
		1s 的时候时间戳可以执行,定时器也可以执行
	}
*/
const throttle = function (fn, delay) {
    let timer = null, previous = 0

    return function () {
        const context = this, args = arguments
        let now = +new Date()
        let remaining = delay - (now - previous)

        if (remaining <= 0) {
     // 这里选择的是时间戳的优先级更高,可根据上面的 example进行理解
        	clearTimeout(timer)
            timer = null
            
            fn.apply(context, args)
            previous = +new Date() // 更新时间戳
        } else if(!timer){
            timer = setTimeout(() => {
                previous = +new Date()
                fn.apply(this, args)
                timer = null
            }, remaining)
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值