防抖节流详细用法和区别 - 详解版

前言
防抖节流是个老生常谈的话题,也是面试出现频率极高的问题。对于防抖节流,网上资料很多,但是感觉好多文档讲的不是很清晰。本文将从防抖节流概念开始,到具体实现的代码解析,一步一步讲述如何实现防抖节流,如有不同的意见欢迎讨论。
本次讲解防抖节流将使用vue方式,但是其核心代码跟普通js代码一致,看具体需求对应实现即可。

概念

防抖:

规定时间内只触发一次,在规定时间内触发,会取消原来计时,开始重新计时。
节流:
规定时间内只触发一次。如果规定时间内再次触发,则什么都不做,也不会重新计时。

相同目的:
解决短时间内高频触发事件导致响应速度跟不上频率,从而出现延时、停顿、卡死等现象。

区别:
防抖是将多次执行变成最后一次执行;节流是将多次执行变为每隔一段时间执行一次。

应用场景

防抖:

input框搜索联想词、登录、点击按钮提交表单、点赞、收藏、标心…

节流:

scroll滑动事件、resize浏览器窗口改变事件、mousemove鼠标移动事件、文档编辑隔一段时间自动保存…

实现方式

防抖
防抖有两种实现方式:非立即执行版立即执行版

非立即执行版:事件触发 -> 延时 -> 执行回调函数。如果延时过程中事件再次被触发,则请控制之前计时器重新计时。没有触发则等延迟结束后,执行回调函数。

立即执行版:事件触发 -> 立即执行 -> 延时。延时过程中事件被触发则继续执行延时,延时结束后不会执行回调函数(比如表单提交,点赞,收藏)。

代码实现:

<input type="text" v-model="value" @input="changeInput()">
<script>
export default {
    data () {
        return {
            value: '',
            changeInput: () => {}
        }
    },
    mounted () {
    	// 防抖
        this.changeInput = this.debouce(this.submit, 1000, true)
        // 节流
        this.changeInput = this.throttle(this.submit, 1000)
    },
    methods:{
    // 以下代码在这里面即可。
	}
</script>   

非立即执行版;

	debouce (fn, time) {
            let timer = null
            let that = this
            let arg = arguments
            return function () {
            	// 可以这么写
               if (timer) clearTimeout(timer)
               timer = setTimeout(function () {
                   fn.apply(that, arg)
               }, time)
               // 或者
               clearTimeout(fn.timer) // 这种方式会少一次判断
                // setTimeout可以使用箭头函数,这样的话就不用让fn()更改this指向了
               fn.timer = setTimeout(() => {
				fn()
			   })
            }
        },

立即执行版:

debouce (fn, time, imme = true) {
            let timer = null
            let that = this
            let arg = arguments
            return function () {
                if (timer) clearInterval(timer)
                if (imme) {
                    if (!timer) fn.apply(that, arg)
                    timer = setTimeout(() => {
                    	fn.apply(that, arg)
                        timer = null
                    }, time)
                }
            }
        },

结合版:
(通过传imme为true或者false判断是否是立即执行)

debouce (fn, time, imme=true) {
            let that = this
            let timer = null
            let arg = arguments
            return function () {
                if (timer) clearTimeout(timer)
                if (imme) {
                    if (!timer) fn.apply(that, arg)
                    // 这里为什么要加if判断:立即执行,延时时间没过,timer一直有值,延时时间过了,timer重置为null,才会执行。
                    // 不加if判断,将会每次都执行,防抖就失去意义了。
                    	timer = setTimeout(function() {
                    	fn.apply(that, arg) // 加这行代码,第一次最后一次都会执行
                        timer = null;
                    }, time)

                } else { // 非立即执行,imme设置为false时候触发
                    timer = setTimeout(function () {
                        fn.apply(that, arg)
                    }, time)
                }
            }
        },

节流:
节流有三种实现方式:首节流尾节流兼顾型节流

首节流:时间戳版
通过闭包保存上一次时间的时间戳,然后跟当前触发的时间戳相比较,如果大于规定时间,则执行回调,否则什么也不处理。
特点:第一次会立即执行,之后是超过规定时间才会执行一次。最后一次触发事件不会执行(说明:如果你最后一次触发事件大于规定时间,这样就算不上是连续触发了;小于规定时间,这时候定时器还有值,也不会执行。)

尾节流:定时器版
通过闭包保存上一次计时器状态,未到规定时间,定时器存在;到了规定时间,定时器设置为null。判断如果是null状态(说明此时间隔时间已经大于规定时间)则新增定时器,到时间后执行回调函数。特点:不会立即执行,到规定时间了才会执行。

总结:第一种是停止触发后不会再执行事件,第二种是停止触发后依然会再执行一次事件。

首节流(时间戳版):

throttle (fn, time) {
            let last = 0;
            let that = this
            let arg = arguments
            return function () {
                let now = Date.now()
                // 每次都会执行这里面的方法,所以每次的时间都是不一样的,每次触发时间不同。
                // 如果用外面的,last等于now后,两者相等不会再改变,节流将不起作用。
                if (now - last > time) {
                    fn.apply(that, arg)
                    last = now
                }
            }
        },

尾节流(定时器版):

throttle (fn, time) {
            let timer = null
            return function () {
                if (!timer) {
                    // 如果不加这个判断,那么timer延时器设置不设置将没什么意义,每次timer将生成个新的计时器,将会一直执行,节流失效;
                    // 加上的话,只有null情况下才会重新生成延时器,每次规定时间后重新生成并执行方法,也就达到了每隔1秒执行一次的效果。
                    timer = setTimeout(() => {
                        fn()
                        timer = null
                    }, time)
                }
            }
        },

兼顾型节流:
(既想立即执行,又想停止触发后再次执行,就使用这个)

throttle (fn, time) {
            let last = 0;
            let timer = null;
            let that = this;
            let arg = arguments;
            return function () {
                let now = Date.now()
                clearTimeout(timer)
                // 清除之前设置的计时器(不清除的话,每次都生成新的计时器,每次都执行方法,节流将不起作用)
                if (now - last >= time) { // 延时规定时间到了,执行回调
                    fn.apply(that, arg)
                    last = now
                } else { // 由于之前清除了计时器,这里代码将最后一次执行。
                    timer = setTimeout(function () {
                        fn.apply(that, arg)
                        timer = null
                    }, time)
                }
            }
        },

arguments的作用?
通过arguments可以获取到所有传递给函数的参数,无论是否有形参接收。所以命名函数不是必须的。
arguments是个类数组,有lenth属性,可以通过下标访问。
命名参数被修改,更新会同步到auguments相应位置(严格模式下)。但它们并不访问相同的内存空间,只是值会同步。
箭头函数没有arguments。
所以,fn.apply(this, arguments)其实是将不确定变量替换到函数中了。

小结:
不论你觉得这篇文章 叙述点多显得杂乱也好,详细描述也好,怎么理解都可以。
最终目的还是希望阅读这篇文章的你能更清晰地明白两者的区别和如何使用。本文就分享到这里啦!
  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值