4

前言

以前准备面试、看面经的时候,总会在心里骂,怎么这么多沙雕题目,这不是特意为难我胖虎吗
为难胖虎.jpeg
参加工作一年多了,发现很多面试题在工作中真的会明的暗的碰到。
能让我等菜鸟提前知道这么多概念,为我们指明debbug的方向,真是煞费了前人的一番苦心呀。

这两天遇到一个bug正好是利用节流的思想解决的,趁此机会正好把我对防抖节流思想的理解和应用整理一下,写出了,希望能在工作和面试中帮到大家

防抖(debounce)和节流(throttle)很容易搞混,定义极其晦涩。因此我们先从应用场景入手
---------- 2021.4.27更新
之前写的代码稍微有点问题,这里做了更正,并且参考lodash给了测试用例

防抖(debounce)

应用场景

搜素框,输入框

debounce.gif

在输入文字的时候我们进行某些处理,如果直接绑定处理函数,则每次输入都会处理一次,造成性能浪费,我们实际希望的是用户输入完了或者暂时输入完了我们再去处理,所以需要延迟执行,然后如果一直输入我们则不断去刷新这个定时器,直到停止输入了再延迟处理

测试用例

lodash里面的debounce是这种的

 _.debounce(func, [wait=0], [options=])
 [options.leading=false] (boolean): 指定在延迟开始前调用。
 [options.trailing=true] (boolean): 指定在延迟结束后调用。

为了体现防抖思想,我们实现的是options.leading=false options.trailing=true的简化版本
const debounced_fn = debounce(fn,wait)
debounced_fn 应该表现的能力有:

  1. fn异步执行
  2. 单位时间内,前面的异步执行总会被后面的异步执行给替换
  3. debounced_fn的返回值应该是最近一次的fn的执行结果

参考loadsh的测试用例给出我们的测试用例

const {debounce} = require('./index')

test('debounce',done=>{
    const fn = jest.fn(val=>val)
    const debounced_fn = debounce(fn,16)
    
    var results = [debounced_fn('a'), debounced_fn('b'), debounced_fn('c')];
    // 测试异步执行
    expect(fn.mock.calls.length).toBe(0)
    expect(results).toEqual([undefined,undefined,undefined])
    setTimeout(()=>{
        // 测试防抖(单位时间多次执行只执行最后一次)
        expect(fn.mock.calls.length).toBe(1)
        results = [debounced_fn('d'), debounced_fn('e'), debounced_fn('f')];

    },32)
    setTimeout(()=>{
        expect(fn.mock.calls.length).toBe(2)
        // 测试返回值及参数
        expect(results).toEqual(['c','c','c'])
        done()
    },64)
})

跑lodash的debounce没问题,,现在让我们自己来实现一个

实现

其实挺简单的,就是一个定时器,注意触发的时候清除就行了,注意this,参数,返回值即可
用闭包保存定时器和结果

let debounce = function(fn,delay){
    let timer,res
    return (...args)=>{
            // fn绑定的是这里的this
            clearTimeout(timer)
            timer = setTimeout(()=>{
                res = fn(...args)
            },delay)
        return res
    }
    
}

image.png

节流(throttle)

应用场景

需要控制触发频率的时候
比如登录按钮,用户登录的时候点一下,假设报错,账号密码错误
如果用户手贱,一直狂点,会一直出提示,需要优化一下,先看下效果

未使用节流:重复动作忒多
未节流.gif
未使用节流:动作一定时间内只执行一次
节流.gif

测试用例

这里我们为了体现中心思想,还是做一个简化。写的是options.leading=true, options.trailing=false版本

测试用例
1.立刻执行
2.没有延迟执行
3.单位时间控制触发频率
4.随后调用的函数返回是最后一次 func 调用的结果

const {throttle} = require('./index')

test('throttle',done=>{
    const fn = jest.fn(val=>val)
    //注意如果引入的lodash必须这样写,开始参数设置 ,否则64ms的调用次数是3不是2
    // const debounced_fn = throttle(fn,16,{'trailing':false})
    const debounced_fn = throttle(fn,16)
    results = [debounced_fn('a'), debounced_fn('b')];
    expect(results).toEqual(['a', 'a']);
    expect(fn.mock.calls.length).toBe(1)


    setTimeout(function() {
        var results = [debounced_fn('c'), debounced_fn('d')];
        expect(results).toEqual(['c', 'c']);
        expect(fn.mock.calls.length).toBe(2)
        done();
      }, 64);
})

用lodash的throttle跑没问题,现在让我们自己来实现一个

实现

首先throttle完整实现挺复杂的,不便理解实质。如果想看完整版建议看这篇文章JavaScript专题之跟着 underscore 学节流
我们这里写个最简单的版本

怎么实现throttle(fn,delay)这个函数呢,单位时间内只能执行一次,换句话说当前执行的时候与前一次执行时间差必须大于delay才让执行
我们要保存上一次执行时间,咋保存呀?通过闭包

let throttle = function(fn,frequency){
    let lastTime = 0,res
    return (...args)=>{
            let currentTime = +new Date()
            if(currentTime-lastTime>frequency){
                res = fn(...args)
                lastTime = currentTime
            }
            return res
    }   
}

image.png

防抖节流区别

看起来防抖节流都是解决动作重复触发产生的性能问题的,但是他们的原理是不同的

从目标上说
debounce 是延迟执行,刷新延迟(理论上如果我一直动是不是可以一直不执行?)
throttle 是立刻执行,限制执行频率,(单位时间肯定得执行,应该不会有人会无聊到把间隔时间设置为无限长吧)

从返回值上说
单位时间(小于delay)内频繁执行的话
debounce 中func 执行的是最后一次,所以返回结果是最后一次的结果
throttle 中func 执行第一次,所以返回结果是最后一次的结果
当然debouncedfunc本身每次都是有返回值的,为上一次func的执行结果
看不懂的自己研究下我的测试用例

从极端情况上说
当动作一直持续下去,debounce永远不会执行函数,throttle会定期执行

从使用场景上说
debounce: 输入框 throttle:控制触发频率 比如报错,点击

解决一个开发问题

现在运用我们所学解决上个星期我遇到的一个场景
我在axios里面有个响应拦截器,遇到后端返回的状态码为xx(没有权限)会跳到登录页,报错没有权限
但是有个问题,在某些页面我会不止发一个请求,每个请求都会受到没有权限的响应引起报错,所以跳到登录页我会受到N个没有权限的提示
初级知识盲区.png

怎么做?用debounce还是throttle?

我用的throttle解决的,3秒内如果是权限问题,只让报一次错

//fn是权限错误的相关逻辑
const throttlefn = throttle(fn,3000,{trailing:false})
service.interceptors.response.use(
  async res => {
    if (!res.data) return null
    const { data: { msg, status } } = res
    switch (status) {
      case OK:...
      case NOT_LOGIN:case LoginFailed:case InvalidRole:case INVALID_PWD:
        throttlefn(msg)
        break
      case NONE_DATA:...
      default:...
    }
  },...)

结尾

今天关于节流防抖的优化就分享到这了。由于技术有限,如果阅读中发现有什么错误,请在留言指出
如果你觉得本文对你有很大的帮助,求点赞,求收藏,求打赏,你们的支持是作者写作的最大动力!


Runningfyy
1.3k 声望661 粉丝