前言
以前准备面试、看面经的时候,总会在心里骂,怎么这么多沙雕题目,这不是特意为难我胖虎吗
参加工作一年多了,发现很多面试题在工作中真的会明的暗的碰到。
能让我等菜鸟提前知道这么多概念,为我们指明debbug的方向,真是煞费了前人的一番苦心呀。
这两天遇到一个bug正好是利用节流的思想解决的,趁此机会正好把我对防抖节流思想的理解和应用整理一下,写出了,希望能在工作和面试中帮到大家
防抖(debounce)和节流(throttle)很容易搞混,定义极其晦涩。因此我们先从应用场景入手
---------- 2021.4.27更新
之前写的代码稍微有点问题,这里做了更正,并且参考lodash给了测试用例
防抖(debounce)
应用场景
搜素框,输入框
在输入文字的时候我们进行某些处理,如果直接绑定处理函数,则每次输入都会处理一次,造成性能浪费,我们实际希望的是用户输入完了或者暂时输入完了我们再去处理,所以需要延迟执行,然后如果一直输入我们则不断去刷新这个定时器,直到停止输入了再延迟处理
测试用例
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 应该表现的能力有:
- fn异步执行
- 单位时间内,前面的异步执行总会被后面的异步执行给
替换
掉 - 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
}
}
节流(throttle)
应用场景
需要控制触发频率的时候
比如登录按钮,用户登录的时候点一下,假设报错,账号密码错误
如果用户手贱,一直狂点,会一直出提示,需要优化一下,先看下效果
未使用节流:重复动作忒多
未使用节流:动作一定时间内只执行一次
测试用例
这里我们为了体现中心思想,还是做一个简化。写的是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
}
}
防抖节流区别
看起来防抖节流都是解决动作重复触发产生的性能问题的,但是他们的原理是不同的
从目标上说
debounce 是延迟执行,刷新延迟(理论上如果我一直动是不是可以一直不执行?)
throttle 是立刻执行,限制执行频率,(单位时间肯定得执行,应该不会有人会无聊到把间隔时间设置为无限长吧)
从返回值上说
单位时间(小于delay)内频繁执行的话
debounce 中func 执行的是最后一次,所以返回结果是最后一次的结果
throttle 中func 执行第一次,所以返回结果是最后一次的结果
当然debouncedfunc本身每次都是有返回值的,为上一次func的执行结果
看不懂的自己研究下我的测试用例
从极端情况上说
当动作一直持续下去,debounce永远不会执行函数,throttle会定期执行
从使用场景上说
debounce: 输入框 throttle:控制触发频率 比如报错,点击
解决一个开发问题
现在运用我们所学解决上个星期我遇到的一个场景
我在axios里面有个响应拦截器,遇到后端返回的状态码为xx(没有权限)会跳到登录页,报错没有权限
但是有个问题,在某些页面我会不止发一个请求,每个请求都会受到没有权限的响应引起报错,所以跳到登录页我会受到N个没有权限的提示
怎么做?用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:...
}
},...)
结尾
今天关于节流防抖的优化就分享到这了。由于技术有限,如果阅读中发现有什么错误,请在留言指出
如果你觉得本文对你有很大的帮助,求点赞,求收藏,求打赏,你们的支持是作者写作的最大动力!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。