这一篇文章我想写一下防抖与节流,因为我自己不是很理解而且说实话,以前知道,但是老忘,虽然概念还有一些简单的写法会,但还是缺乏练习和深刻的理解。
当我们不加防抖或节流的时候就像这样,鼠标滑过触发这么多次,所以我们一个使用防抖或节流来限制它的请求次数而不是触发次数。关于防抖与节流的应用和解释自行查找资料。
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)。