首先什么是防抖?为什么要防抖?
某天晚上大春和马冬梅决定偷偷FQ去看望受伤的夏洛,在夏洛窗外,冬梅让大春放哨,看见有人过来了就说一声,当冬梅FQ一半的时候,大春刚好看到一个拿三叉戟的黑影出现在电话亭那边,大春很着急,立马告诉冬梅“冬梅啊有人来了要不咱们撤吧,冬梅啊有人来了要不咱们撤吧,冬梅啊有人来了要不咱们撤吧.....”。
好回到正题,防抖之前:大春反复的说“冬梅啊有人来了要不咱们撤吧”,防抖之后:就一句“冬梅啊有人来了要不咱们撤吧”。所以所谓
防抖也就是防止事件没必要的重复触发
。
为什么要防抖,其实这个问题以前自己肯定是遇到这种场景的,只是当时能力还不够来考虑这种性能优化的问题。例如:使用vue时@input事件绑定在input组件上随意输入一个内容他都会触发事件对吧,那我就想要输入完成后再触发嘞(不杠@change), 再例如监听滚动事件 scroll 时会密集的触发对吧,那我就是不想让他密集的触发。还有浏览器窗口大小改变的事件resize、keypress、mousemove等等都是高频事件,他们在触发时会不断地调用绑定在事件上的回调函数,极大地浪费资源,降低前端性能。
像 scroll 这种高频事件整体的触发完成之后,再进行事件操作,这才是我想要的。就是所谓的“防抖”,其本质就是优化高频执行代码的一种手段
。
什么是节流?为什么要节流?
这时候在墙上的冬梅瞅了瞅原来是个渔夫在那里打电话而已,告诉大春“大春没事儿,他不是保安,真走过来了你再告诉我”,不一会大春又看到那渔夫真过来了,一步两步好像在跳舞边跳边走,于是大春跟着他的节奏,走两步就告诉冬梅“冬梅那渔夫好像过来了”,走两步就告诉冬梅“冬梅那渔夫好像过来了”......。
好回到正题,节流之前:大春反复的说“冬梅那渔夫好像过来了”,节流之后:大春跟着节奏每两步才说。
还有个栗子防抖就像王者的回城,打断了就要重新来,节流是技能冷却,需要等cd时间过了才能继续使用技能。我王者这个栗子感觉不是特别准确,看怎么理解吧。
节流跟防抖差不太多,区别在于防抖是避免事件没必要地大量重复执行,而节流是这种“避免”放松一点,在事件连续触发的时候规定时间间隔每执行一次就好,例如给scroll事件设置防抖那他从开始滚动到最后也就执行一次,而给scroll事件设置节流则是在开始滚动到结束滚动的时间内定时轮番的去执行操作。这就是节流。
区别
节流和防抖的区别:
-
防抖是 多次触发,只执行最后一次。适用于只需要一次触发生效的场景。
-
节流是 每隔一段时间触发一次操作。适用于多次触发要多次生效的场景。
应用场景:
-
debounce
-
search搜索联想,用户在不断输入值时,用防抖来节约请求资源。
-
window触发resize的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次
-
-
throttle
-
鼠标不断点击触发,mousedown(单位时间内只触发一次)
-
监听scroll滚动事件,比如是否滑到底部自动加载更多,用throttle来判断
-
防抖debounce
大致思路:通过定时器(延迟器)实现,第一次触发之后设置好定时器,如果之后再有该事件触发那就把上一次的定时器清理再重新设置定时器,定时时间到则执行目标操作,如此反复。
非立即执行版本
先看一种比较简单的防抖实现(非立即执行版本),就是需要经过定时器的延迟才会执行。
// 防抖 定时器实现(非立即执行版本)
function debounce(fn, delay = 200) {
let timer = null
return function() {
if(timer){ // 如果设置过了定时器
clearTimeout(timer)
}
timer = setTimeout(() => {
fn.apply(this, arguments); // 透传 this和参数
timer = null
},delay)
}
}
立即执行版本
再看立即执行版本:立即执行的意思是触发事件后函数会立即执行,然后 n 秒内不触发事件才能继续执行函数的效果。
// 防抖 定时器实现(立即执行版本)
function debounce(fn, delay = 200) {
let timer = null
return function () {
let args = arguments
let now = !timer
timer && clearTimeout(timer) //timer不为null时执行clearTimeout函数
timer = setTimeout(() => {
timer = null
}, delay)
if (now) {
fn.apply(this, args)
} //或者:now && fn.apply(this, args)
}
}
上边调用设置之后,在绑定事件的时候这样使用就好
window.onscroll = debounce(lozyLoad,1000); //lozyLoad是想要执行的回调函数
节流throttle
非立即执行版本
节流的简单实现:
// 节流函数(简单版本):第一次触发时不会执行,而是在delay毫秒之后才执行
function throttle(fn, delay = 200) {
let timer = 0
return function () {
if(timer){
return
}
timer = setTimeout(() =>{
fn.apply(this, arguments); // 透传 this和参数
timer = 0
},delay)
}
}
立即执行版本
也是节流的简单实现:
//节流函数(时间戳版):触发事件时立即执行,以后每过delay毫秒之后才执行一次,并且最后一次触发事件若不满足要求不会被执行
function throttle(fn ,delay = 200){
let oldtime = Date.now();
return function(){
let context = this;
let args = arguments;
let newtime = Date.now();
if(newtime - oldtime >= delay){
fn.apply(context,args);
oldtime = Date.now();
}
}
}
精准节流方案
其实就是将以上两种方式相结合,实现一个更加精准的节流:第一次会马上执行,最后一次也会执行
//节流函数:定时器和时间戳结合版本
function throttle(fn ,delay = 200){
let timer = null;
let starttime = Date.now();
return function(){
let curTime = Date.now();
let remaining = delay -(curTime - starttime);
let context = this ;
let args = arguments;
clearTimeout(timer);
if(remaining <= 0) {
fn.apply(context,args);
starttime = Date.now();
}else{
timer = setTimeout(fn,remaining);
}
}
}
可以参考这篇文章对代码有更详细的注释: