这一篇文章我想写一下防抖与节流,因为我自己不是很理解而且说实话,以前知道,但是老忘,虽然概念还有一些简单的写法会,但还是缺乏练习和深刻的理解。
当我们不加防抖或节流的时候就像这样,鼠标滑过触发这么多次,所以我们一个使用防抖或节流来限制它的请求次数而不是触发次数。关于防抖与节流的应用和解释自行查找资料。
<div id=”app”></div>
function fn(e) {
console.log(this);
console.log(e);
app.innerHTML = num ++;
}
1. 防抖
简单实现
function debounce(fn, delay) {
let timer = null;
return function(){
clearTimeout(timer);
timer = setTimeout(function () {
fn();
}, delay);
}
}
使用节流后
app.onmousemove = fdebounce(fn, 1000);
我们发现次数减少了太多,因为只要你在 delay 时间内不停地触发就不会执行直到你的间隔时间大于 delay 才会执行。
我们来看一下 this 和 event。this 是 window,event 是 undefined。
修复 this 指向和事件参数
function debounce(fn, delay) {
let timer = null,
that;
return function(e){
that = this;
clearTimeout(timer);
timer = setTimeout(function () {//this 指向 window(非严格模式)
fn.apply(that, [e]);
}, delay);
}
}
或者是
function debounce(fn, delay) {
let timer = null;
return function(e){
clearTimeout(timer);
timer = setTimeout(()=>{ // 箭头函数
fn.apply(this, [e]);
}, delay);
}
}
增强版(是否立即执行)
function debounce(fn, delay, immediate) {
let timer = null,
that;
return function (e) {
that = this;
clearTimeout(timer);
if(immediate){// 是否立即执行
if(!timer){// 如果没有设置定时器就先执行
fn.apply(that, [e]);
}
timer = setTimeout(function () {
timer = null;// 设置定时器,使其在 delay 毫秒内不能执行,过了 delay 毫秒后在执行
}, delay);
}else{// 否则延时执行
timer = setTimeout(function () {
fn.apply(that, [e])
}, delay);
}
};
}
// 这个 if…else 只能执行一个,要不先执行,要不延时执行
一开始会执行一次,因为录制不能显示鼠标所以理解一下。
节流
简易版(时间戳)
function throttle(fn, delay) {
let last = 0, // 上次执行时间
now; // 执行时的时间
return function (e) {
now = Date.now(); // 当前时间
if(now – last >= delay){// 如果两次时间间隔大于 delay,就执行
last = now; // 重新赋值
fn();
}
};
}
在规定时间 delay 毫秒内总会执行一次事件。
setTimeout 版本(修复 this 指向和事件参数)
function throttle(fn, delay) {
let that,
timer = null;
return function (e) {
that = this;
if(!timer){// 如果没设置定时器就执行
timer = setTimeout(function () {
fn.apply(that, [e]);
timer = null; // 果 delay 毫秒就设置 timer,这样就能 delay 执行一次
}, delay);
}
};
}
修复 this 指向和事件参数(时间戳)
function throttle(fn, delay) {
let last = 0, // 上次执行时间
now; // 执行时的时间
return function (e) {
now = Date.now(); // 当前时间
if(now – last >= delay){// 如果两次时间间隔大于 delay,就执行
last = now; // 重新赋值
fn.apply(this, [e]);
}
};
}
区别
时间戳版本的会先执行
setTimeout 版本的会后执行
所以可以结合一下他们两个。
结合版
function throttle(fn, delay) {
let last = 0, // 上次执行时间
now, // 当前时间
leftTime, // 剩余时间
that,
timer = null;
return function (e) {
that = this;
now = Date.now();
leftTime = delay – (now – last);
if(leftTime <= 0){// 保证一开始就执行(先执行)
last = now;
fn.apply(that, [e]);
}else{
timer = setTimeout(function() {// 延时执行
fn.apply(that, [e]);
timer = null;
},delay)
}
};
}
这样做总体思路没错,但是第一次会执行以后就是两个一起执行,因为条件都满足。
修改
function throttle(fn, delay) {
let last = 0,
now,
leftTime,
that,
timer = null;
return function (e) {
that = this;
now = Date.now();
leftTime = delay – (now – last);
if(leftTime <= 0){
if(timer){// 如果有定时器就清除
clearTimeout(timer);
timer = null;
}
last = now;
fn.apply(that, [e]);
}else if(!timer){
timer = setTimeout(function() {
last = now; // 如果时间满足就让他不满足
// 总之除了第一次就只让其中一个执行
fn.apply(that, [e]);
timer = null;
},delay)
}
};
}
一开始执行一次(时间戳),最后停止在执行一次(setTimeOut)。