昨天看博主分享面试经历,有个防抖和节流。所以,故在此一边学习,一边分享。希望对你们有所帮助吧。什么不会,找度娘。相信,比你优秀的人始终很多。
在绑定 scroll 、resize 这类事件时,当它发生时,它被触发的频次非常高,间隔很近。如果事件中涉及到大量的位置计算、DOM 操作、元素重绘等工作且这些工作无法在下一个 scroll 事件触发前完成,就会造成浏览器掉帧。加之用户鼠标滚动往往是连续的,就会持续触发 scroll 事件导致掉帧扩大、浏览器 CPU 使用率增加、用户体验受到影响。尤其是在涉及与后端的交互中,前端依赖于某种事件如resize,scroll,发送Http请求,在这个过程中,如果不做防抖处理,那么在事件触发的一瞬间,会有很多个请求发过去,增加了服务端的压力。当然搜索框input实时监测也是这样,每次输入框发生变化也是每次请求接口。
针对此类快速连续触发和不可控的高频触发问题,debounce 和 throttling 给出了两种解决策略;
一、函数防抖(debounce)
策略是当事件被触发时,设定一个周期延迟执行动作,若期间又被触发,则重新设定周期,直到周期结束,执行动作。 这是debounce的基本思想,在后期又扩展了前缘debounce,即执行动作在前,然后设定周期,周期内有事件被触发,不执行动作,且周期重新设定。附上大佬的视图
延迟 debounce
前缘 debounce
debounce的特点是当事件快速连续不断触发时,动作只会执行一次。 延迟debounce,是在周期结束时执行,前缘debounce,是在周期开始时执行。但当触发有间断,且间断大于我们设定的时间间隔时,动作就会有多次执行。
版本1:周期内有新事件触发,清除旧定时器,重置新定时器;这种方法,需要高频的创建定时器
// 暴力版: 定时器期间,有新操作时,清空旧定时器,重设新定时器
var debounce = (fn, wait) => {
let timer, timeStamp=0;
let context, args;
let run = ()=>{
timer= setTimeout(()=>{
fn.apply(context,args);
},wait);
}
let clean = () => {
clearTimeout(timer);
}
return function(){
context=this;
args=arguments;
let now = (new Date()).getTime();
if(now-timeStamp < wait){
console.log('reset',now);
clean(); // clear running timer
run(); // reset new timer from current time
}else{
console.log('set',now);
run(); // last timer alreay executed, set a new timer
}
timeStamp=now;
}
}
版本2:周期内有新事件触发,重置定时器开始时间戳,定时器执行时,判断开始时间戳,若开始时间戳被推后,重新设定延迟定时器。
/ 优化版: 定时器执行时,判断start time 是否向后推迟了,若是,设置延迟定时器
var debounce = (fn, wait) => {
let timer, startTimeStamp=0;
let context, args;
let run = (timerInterval)=>{
timer= setTimeout(()=>{
let now = (new Date()).getTime();
let interval=now-startTimeStamp
if(interval<timerInterval){ // the timer start time has been reset, so the interval is less than timerInterval
console.log('debounce reset',timerInterval-interval);
startTimeStamp=now;
run(timerInterval-interval); // reset timer for left time
}else{
fn.apply(context,args);
clearTimeout(timer);
timer=null;
}
},timerInterval);
}
return function(){
context=this;
args=arguments;
let now = (new Date()).getTime();
startTimeStamp=now;
if(!timer){
console.log('debounce set',wait);
run(wait); // last timer alreay executed, set a new timer
}
}
}
版本3:在版本2基础上增加是否立即执行选项
// 增加前缘触发功能
var debounce = (fn, wait, immediate=false) => {
let timer, startTimeStamp=0;
let context, args;
let run = (timerInterval)=>{
timer= setTimeout(()=>{
let now = (new Date()).getTime();
let interval=now-startTimeStamp
if(interval<timerInterval){ // the timer start time has been reset,so the interval is less than timerInterval
console.log('debounce reset',timerInterval-interval);
startTimeStamp=now;
run(timerInterval-interval); // reset timer for left time
}else{
if(!immediate){
fn.apply(context,args);
}
clearTimeout(timer);
timer=null;
}
},timerInterval);
}
return function(){
context=this;
args=arguments;
let now = (new Date()).getTime();
startTimeStamp=now; // set timer start time
if(!timer){
console.log('debounce set',wait);
if(immediate) {
fn.apply(context,args);
}
run(wait); // last timer alreay executed, set a new timer
}
}
}
二、函数节流(throttle)
throttling,节流的策略是,固定周期内,只执行一次动作,若有新事件触发,不执行。周期结束后,又有事件触发,开始新的周期。 节流策略也分前缘和延迟两种。与debounce类似,延迟是指 周期结束后执行动作,前缘是指执行动作后再开始周期。
延迟throttle
前缘throttle
版本1:简单版
简单版: 定时器期间,只执行最后一次操作
var throttling = (fn, wait) => {
let timer;
let context, args;
let run = () => {
timer=setTimeout(()=>{
fn.apply(context,args);
clearTimeout(timer);
timer=null;
},wait);
}
return function () {
context=this;
args=arguments;
if(!timer){
console.log("throttle, set");
run();
}else{
console.log("throttle, ignore");
}
}
}
版本2:增加前缘选项
/// 增加前缘
var throttling = (fn, wait, immediate) => {
let timer, timeStamp=0;
let context, args;
let run = () => {
timer=setTimeout(()=>{
if(!immediate){
fn.apply(context,args);
}
clearTimeout(timer);
timer=null;
},wait);
}
return function () {
context=this;
args=arguments;
if(!timer){
console.log("throttle, set");
if(immediate){
fn.apply(context,args);
}
run();
}else{
console.log("throttle, ignore");
}
}
}
总结
函数防抖:将几次操作合并为一此操作进行。原理是维护一个计时器,规定在delay时间后触发函数,但是在delay时间内再次触发的话,就会取消之前的计时器而重新设置。这样一来,只有最后一次操作能被触发。
函数节流:使得一定时间内只触发一次函数。原理是通过判断是否到达一定时间来触发函数。
区别: 函数节流不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数,而函数防抖只是在最后一次事件后才触发一次函数。 比如在页面的无限加载场景下,我们需要用户在滚动页面时,每隔一段时间发一次 Ajax 请求,而不是在用户停下滚动页面操作时才去请求数据。这样的场景,就适合用节流技术来实现。
debounce和throttling 各有特点,在不同 的场景要根据需求合理的选择策略。如果事件触发是高频但是有停顿时,可以选择debounce; 在事件连续不断高频触发时,只能选择throttling,因为debounce可能会导致动作只被执行一次,界面出现跳跃。
先知概念,慢慢消化吧。或者消化不良吧