防抖和节流

防抖和节流是优化频繁触发的函数执行的技术,防止资源浪费。防抖(debounce)将多次执行合并为一次,常用于搜索输入、窗口大小调整等场景。节流(throttle)在固定时间间隔内保证函数执行一次,适用于无限滚动、鼠标移动等。文章通过代码示例解释了两者的实现原理和应用场景,并提到了lodash库和requestAnimationFrame作为替代方案。
摘要由CSDN通过智能技术生成

面试题

  • 谈谈防抖节流
  • 手写防抖节流

防抖与节流是很相似(但不同)的概念,简单来说就是一个能控制一段时间某个函数的执行次数的方案。用来优化计算机或网络资源。下面我们分别看下这两个概念。

防抖 debounce

简单来说就是把多次执行组合成一次执行。

比如我们设置了一个时间间隔 5 秒,当事件触发的间隔超过 5 秒,(回调)函数才会执行,如果在 5 秒内,事件又被触发,则刷新这个 5 秒,至少5秒后事件没被触发才执行函数。
实现原理

//---------------------------------测试用例--------------------------------
// 用户高频率执行的函数(需要防抖的函数),但可能是个异步请求列表,成本比较高需要优化
function userHighRequencyAction(e, content) {
    console.log(e, content);
}

// 给这个高频的方法,加防抖方案输出一个防抖的function
var userDebounceAction = debounce(userHighRequencyAction, 1000);

// 如何触发那个高频函数 绑定一个onmousemove事件,来模拟高频触发  $(1)事件监听$
document.onmousemove = function (e) {
    userDebounceAction(e, 'test debounce'); // 给防抖函数传个参
}
//---------------------------------实现-----------------------------------

function debounce(func, wait) {              // -----> $(2)作用域和闭包$
    let timer; 
    return function () { 
        let context = this;                 
        let args = arguments;
        if (timer) {
            clearTimeout(timer);
        }
        timer = setTimeout(function () {
            func.apply(context, args);       // -----> $(3)this$
            // 其实就是 context.func(args)
        }, wait);
    };
}

看了上面的实现即使有解释,我想你也是云里雾里,为什么要这么做?有什么好处?其实是你需要理解清楚一些其他必要的概念。和那些必要概念与之的必要关联。看不懂没关系,多看两遍,多思考关联,知识是一张网,继续下去,你就能把知识融汇贯通。

关联概念

  • 事件监听
  • 作用域
  • 闭包
  • this

理解了这些概念后,再来简单说明下这实现思路。你会觉得,下面我这段话异常清晰直接,如果觉得模糊,再多在关联概念里游会,回来后你会变得更健壮。

简单说明
其实这其中逻辑没什么好说的,就是利用 setTimeout 这个WebApi的延迟效果,设置一个定时器,当没有到达延迟时间时重复执行就清除定时器(clearTimeout),并建立一个新的定时器,继续等延迟时间,如此循环。

  1. 为什么要返回函数或者说为什么要用 闭包【关联概念】。其实debounce函数只调用了一次,后面调用的全是闭包函数,其实了解闭包都知道这样 timer 定时器的变量不被gc回收,这样下次执行时仍然指向的是上一次设置的定时器。
  2. 为什么要绑定 this, 因为fn 执行的时候this指向全局对象(浏览器中是window),根据词法作用域,可以在外层用个变量保存下 this, 再用 apply 进行显示绑定。
  3. 为什么要有 arguments 因为 JavaScript 在事件处理函数中会提供事件对象 event, 所以我们得把参数一并传入, 而apply/call是可以传参的(具体自查MDN的api)。

这样理解是不是轻松多了,所以对概念之间关联的理解跟清晰的概念本身同样重要。

**

类比
乘公交车,一直有人陆陆续续上车(事件触发),司机心想30s(时间间隔) 内没人继续上,再开车(函数执行)。提高了公交的资源利用。
乘电梯,程序设定电梯在没有人20s(时间间隔) 内按开门按钮(事件触发)上下电梯,再关门启动(函数执行)。提高了电梯的资源利用。
我们开发中可能遇见的场景

  • 搜索输入框(Autocomplete),当不再输入后的几百毫秒再去发送请求,减少服务器压力。
  • 注册框(即时判断是否重复用户名),或需要后台校验的文本输入框同理。
  • 提交按钮的点击,有的人就是会疯狂的点,有啥办法呢。
  • 不停改变浏览器窗口大小会触发多次 resize 事件,引起浏览器的重排【关联概念(弱)】,消耗性能。

节流 throttle

简单来说 就是在指定的时间间隔内,只允许我们的函数执行一次。

比如一个事件在被疯狂触发,本来每秒执行几百次(回调)函数,而你使用函数节流设了个时间间隔 1s,那么这个函数在1s 内只会执行一次。
与防抖之间的主要区别
节流至少在每时间间隔内 保证有规律地执行该功能。
实现原理

function throttle(func, wait) {
    var timer, 
    	context, 
        args;
    return function () { 
        context = this;                 
        args = arguments;
        if (!timer) {
          timer = setTimeout(function() {
            // 执行后置定时器变量为null
            timer = null;
            func.apply(context, args);
          }, wait);
        }
    };
}

简单来说,当触发事件的时候,设置一个 timer, 再次触发事件的时候 timer 存在(不为null),则不执行,直到函数执行了,把timer置空,并启动设置下一个定时器。这也就保证了 wait时间内函数只会执行一次。

我们开发中可能遇见的场景

  • 无限滚动列表。用户向下滚动列表时,您需要时刻检查用户屏幕离底部有多远。如果用户接近底部,我们应该请求下一页内容并将其附加到页面上。为什么不用 debounce 因为它仅在用户停止滚动时才会触发。我们需要在用户到达底部之前就开始获取内容。throttle可以保证我们一直在检查距底部的距离。但不用频率过高的执行函数(scroll 事件触发的回调)。
  • 高频点击提交按钮,比如你抢票的时候,用debounce你就会因为太想抢而不停地点确越抢不到。
  • 监听鼠标 mousemove 计算一个div跟随鼠标移动而移动的函数等等, 用debounce就很不连贯

怎么用防抖与节流?

讲道理这个我建议直接用lodash (省的自己写,而且有些变体用法满足多种需求比如 debounce 的前置执行还是后置执行参数)

requestAnimationFrame(rAF)

限制函数执行速率的另一种方法。 根据经验,如果你的JavaScript函数是“绘画”或直接对动画属性进行实时处理或重新计算元素位置使用requestAnimationFrame会是好选择。

debounce 手写简单拓展

虽然lodash 现成 api 但可能面试会问就简单列下这种举一反三的问题,不过你当时想想其实问题不大。例子就用别人现成的了。

需求: 不希望非要等到事件停止触发后才执行,我希望立刻执行函数,然后等到停止触发 n 秒后,才可以重新触发执行。

function debounce(func, wait, immediate) {
    var timeout, result;

    return function () {
        var context = this;
        var args = arguments;
        if (timeout) {
          clearTimeout(timeout);
        }
        if (immediate) {
            // 如果已经执行过,不再执行
            var callNow = !timeout;
            timeout = setTimeout(function(){
                timeout = null;
            }, wait)
            if (callNow) { 
              result = func.apply(context, args)
            }
        }
        else {
            timeout = setTimeout(function(){
                func.apply(context, args)
            }, wait);
        }
        return result;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青night

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值