面试中经常遇到面试官问到,手写一个防抖或者节流吧。
这道题真是说容易也容易,说不容易也不容易的题。
如果你没有自己仔细的研究过,甚至连二者的差别都说不清楚 ,就更别提手写出代码来了。
那下面我们就一起来学习一下防抖和节流分别是什么功能,它们俩又是如何实现的呢。
节流
节流就是控制流量,在某段时间内,只执行一次。
防抖
某段时间内未结束操作,则重新计时,并在规定时间内,仅执行一次。
看起来感觉差不多,
比如浏览器的onscroll,oninput,resize,onkeyup等,如果用户一直操作滚动视窗、缩放浏览器大小,则会重新计算延时,若操作稳定了,则在约定时间后执行一次方法。
行吧。下面先实现一个简单版本的节流:
/**
@func 要节流的函数
@wait 时间间隔
*/
function throttle(func, wait){
let pre = 0;//记录上一次的时间
return function(){
let now = Date.now();//记录当前时间
if(now - pre > wait){ //如果时间差大于约定的时间间隔
func.apply(this, arguments); //则触发
pre = now; //同时将时间更新
}
}
}
参考代码:Easy Throttle
上面是利用时间戳的方式实现,有一个问题存在,第一次触发会执行func,但停止触发后,不会再执行一次func。
我们试着使用定时器来优化一下,并将停止触发后要不要再执行一次作为一个参数trailing传递。
function throttle(func, wait, options) {
let args, context;
let pre = 0;
let timer;
let later = function () {
pre = Date.now();
func.apply(context, args);
args = context = null;
};
let throttled = function () {
args = arguments;
context = this;
let now = Date.now();
let remaining = wait - (now - pre);
if (remaining <= 0) {
//第一次执行,如果已经存在,则清除
if (timer) {
clearTimeout(timer);
timer = null;
}
func.apply(context, args);
pre = now;
} else if (!timer && options.trailing !== false) {
//默认会触发,最后一次应该触发。
timer = setTimeout(later, remaining);
}
};
return throttled;
}
调用时:
let btn = document.getElementById("btn");
btn.addEventListener("click", throttle(logger, 1000, { trailing: true })); //1s内都算一次
function logger() {
console.log("logger");
}
参考代码:停止触发后要不要再次执行
那么既然有限制停止触发后的再次执行,就有对于首次加载时需不需要立即执行的限制,这里我们用参数leading:false来表示一进来不要立即执行。
function throttle(func, wait, options){
let args,context;
let pre = 0;
let timer;
let later = function(){
pre = options.leading === false ? 0:Date.now();
func.apply(context, args);
args = context = null;
}
let throttled = function (){
args = arguments;
context = this;
let now = Date.now();
//一进来不要立刻执行
if(!pre && options.leading === false){
pre = now;
}
let remaining = wait - (now - pre);
if(remaining <= 0){//第一次执行
if(timer){
clearTimeout(timer);
timer = null;
}
func.apply(context, args);
pre = now;
}else if(!timer && options.trailing !== false){//默认会触发,最后一次应该触发。
timer = setTimeout(later, remaining);
}
}
return throttled;
}
调用时:
let btn = document.getElementById("btn");
btn.addEventListener("click", throttle(logger, 1000, { leading: false }));
function logger() {
console.log("logger");
}
参考代码:是否立即执行
防抖:
function debounce(func, wait, immediate){
let timer;
return function(){
clearTimeout(timer);
if(immediate){
let callNow =!timer;
if(callNow) func.apply(this, arguments);
}
timer = setTimeout(()=>{
func.apply(this, arguments)
timer = null;
}, wait);
}
}
调用时:
let btn2 = document.getElementById('btn2');
btn2.addEventListener('click',debounce(logger,1000, true)); //停止 时才算一次
function logger(e){
console.log('logger2',e);
}
参考代码:debounce
好啦,笔记 就写到这里,后续可能会补充一下lodash关于二者合到一起的写法。
今天就点到为止吧。