用吃巧克力来理解 防抖和节流!再也不会忘了!
词语不陌生,但是回回看,回回忘记,究其根本就是没理解。
让我们先忘掉这两个词语,进入一个新的情境~
现在你是一个 5 岁的宝宝,今天有小朋友要来,妈妈准备了很多巧克力。
你一看,赶紧就说,妈妈我要吃巧克力,妈妈给了你一块,你又叨叨了一次,又给了一块。
第三次叨叨的时候,妈妈就不太高兴了,你已经吃了 2 块了,剩下的巧克力要等小朋友来吃。
作为一个 5 岁的宝宝,你难以接受,于是,一直跟着妈妈后面叨叨,“妈妈,我要吃巧克力,我好想吃巧克力”。
妈妈被你烦的不行,就妥协的说,“我定个半小时的计时器,计时结束就给你一块,还想要的话,那我们再设置半小时定时器”。
于是,妈妈就设置了半小时定时器~
但是你只是 5 岁的宝宝啊,心里还是很想很想吃,于是,还是在后面叨叨,但妈妈无视你,带上耳塞了。
半小时计时结束,计时器响了,妈妈果然就给你一块了!
吃完之后,你又去叨叨了,于是妈妈重新设置了半小时的定时器。
节流 throttle
好,现在来根据上面的例子,说说妈妈的策略
你一直跟妈妈要巧克力,如果说一次就给一次,妈妈就要给你无数个巧克力,显然妈妈觉得很累。
于是妈妈拿了个定时器,你叨叨的时候,如果定时器还在计时,就无视你,如果计时器不在计时的话,重新计时。计时结束就给一次。
这样大大减少了满足的频次。
现在切换角色,你是开发者,网页里有个查询的按钮,每次点击就会请求接口,返回新的查询数据。
但有些用户(测试),可能无聊,一直点着查询提交按钮,一秒点个十次,服务器就得返回十次数据。
文章图片
测试就来找你了,“老铁,控制下频次,一秒发一次请求吧”。
这时候,就是节流,你不一定需要记住名词,但你记得怎么控制就行
let times = 0;
const fn = console.log(`发送请求${++times}次`, new Date().toLocaleTimeString());
// 拿一个计时器
var timer;
/* 这里的点击就相当于是叨叨要巧克力
* 每次叨叨的时候,看下计时器在不在计时,
* 如果正在计时的话,就无视
* 如果不在计时的话,就重新开始计时
* 计时结束,妈妈会主动给巧克力,然后关掉计时器
*/
btn.onclick = () => {
// 计时器还在计时,就无视
if (timer) {
return;
}
// 计时器不在计时,就重新开始计时
timer = setTimeout(() => {
// 计时结束的时候,妈妈主动给巧克力,然后关掉计时器
fn();
timer = null;
}, 1000);
};
节流的通用版 把后面的函数用另外一个函数生成,就成了节流的简短版
function throttle(fn, delay) {
// 拿一个计时器
var timer;
return (...args) => {
// 计时器还在计时,就无视
if (timer) {
return;
}
// 计时器不在计时,就重新开始计时
timer = setTimeout(() => {
// 计时结束的时候,妈妈主动给巧克力,然后关掉计时器
fn(...args);
timer = null;
}, delay);
};
}
btn.onclick = throttle(fn, 1000);
文章图片
防抖 【用吃巧克力来理解 防抖和节流!再也不会忘了!】重新回到情境里,你已经连续两个小时都在叨叨了,妈妈想要清净会,于是改了主意~
“这是个定时器,如果你叨叨的话,我就重新开始计时;计时结束,我就给你一个巧克力”
你一听这话,就赶紧闭嘴了
let times = 0;
const fn = console.log(`发送请求${++times}次`, new Date().toLocaleTimeString());
// 拿一个计时器
var timer;
/* 这里的点击就相当于是叨叨要巧克力
* 每次叨叨的时候,计时器就重新计时
* 计时结束,妈妈会主动给巧克力
*/
btn.onclick = () => {
// 只要叨叨,就重新计时
timer && clearTimeout(timer);
timer = setTimeout(() => {
// 计时结束,妈妈主动给巧克力,关掉计时器
fn();
timer = null
}, 1000);
};
文章图片
这就是防抖,叨叨就重新计时,闭嘴了一段时间,才会给巧克力
防抖的通用版 同理,把后面的函数用另外一个函数生成,就成了防抖的简短版
function debounce(fn, delay) {
// 拿一个计时器
var timer = null;
return (...args) => {
// 只要叨叨,就重新计时
timer && clearTimeout(timer);
timer = setTimeout(() => {
// 计时结束,妈妈主动给巧克力,关掉计时器
fn(...args);
timer = null
}, delay);
};
}
btn.onclick = debounce(fn, 1000);
防抖和节流的异同 其实通过上面的情景,我觉得你基本能感受到了这两的异同点。
原始情况:只要叨叨,就满足要求。
缺点就是:满足的太频繁。
现在用节流和防抖的话:
- 节流是拿一个定时器,你叨叨的时候,如果计时器还在计时的话,就无视;如果不在计时的话,就开始计时;
计时结束,主动满足要求。
结果就是:你一直叨叨的话,显然每隔一段时间,你就能满足要求 - 防抖是拿一个定时器,你叨叨的时候,就重新计时;
计时结束,主动满足要求。
结果就是:你一直叨叨的话,显示不会被满足要求,只有停止叨叨,隔一段时间才会被满足要求
不同点:叨叨的时候,隔段时间就满足要求 还是 停止叨叨隔段时间之后,才满足要求。
怎么选择呢 其实想选择哪个,看业务情况和你的心情了!
比如,页面滚动的时候,你想实时知道滚动的高度,但这个实时其实没必要到 10 毫秒的程度,那就想着 50 毫秒获取一次,也就是页面在滚动的时候,你希望每隔 50ms 就获取一次,那就显然是节流~
如果你是希望,页面停止滚动的时候,才需要获取,那就显然是防抖~
滚动可以类比为,一直叨叨要巧克力,获取页面高度相当于给巧克力满足你~
如果没有特别的需求只是单纯的想控制获取次数,随你开心啦~~
常用的场景:
- 点击按钮发请求的时候
- 页面滚动的时候
- 用户在输入文字实时发请求的时候
知道了防抖,另一个就是节流了。
实际怎么使用 虽然简版的能满足一些场景,但一般不建议使用自己手写的,你知道这么个逻辑就行
实际使用的时候,直接使用库里封装好的比较稳妥,以
lodash
为例:// npm i lodash 或者 https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.21/lodash.min.js
btn.onclick = _.throttle(fn, 1000);
btn.onclick = _.debounce(fn, 1000);
这里额外注意两个参数:
- leading,表示开始计时之前,要不要先给你一个巧克力,默认是false,也就是计时开始之前不给巧克力
- trailing,表示计时结束,要不要给你巧克力,默认是true,也就是计时结束给巧克力
引用
- debouncing-and-throttling-in-javascript
推荐阅读
- 9班|9班 刘志雪
- 一个人的碎碎念
- 第326天
- 猎杀IP
- 我从来不做坏事
- 年味真的是越来越淡了么
- 感恩之旅第75天
- 参保人员因患病来不及到指定的医疗机构就医,能否报销医疗费用()
- 雅集
- 我执意要等,是因为我相信你一定会来