持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第13天,点击查看活动详情
参考
使用场所
事件类型 | 事件方法名 |
---|---|
window上的事件 | scroll,resize 等 |
鼠标事件 | mousedown、mousemove、mouseover 等 |
键盘事件 | keyup、keydown |
有时候一个按键容易按多次才响应,如果没做loading
效果的话,会触发多次。
定义
在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时 防抖动是将多次执行变为最后一次执行
生动的例子:公交车司机一分钟内没有其他人上车,就执行关门开车动作。
具体的例子:百度联想搜索框,每次输入关键字,一定时间间隔后会提示出相关的关键字
基础版本
function debounce(func,wait) {
let timeout
return function() {
//if(timeout){clearTimeout(timeout)} 反正不会报错
clearTimeout(timeout)
//setTimeout会把this绑定到全局对象,需要提前绑一下。
//argument传参数。按理说只有一个参数,func.call(this,e)也不错
timeout = setTimeout(()=>func.apply(this,argument),wait)
}
}
function handleScroll(e) {
console.log('事件对象', e)
console.log('this',this)
console.log('滚动了')
}
container.addEventListener('scroll', debounce(handleScroll,1000))
复制代码
立即执行版本
有点do..while
的感觉
之前我以为clearTimeout
之后timeout
就是空了,现在测了一些,是有值的,一串莫名其妙的数字。
function debounce(func, wait, immediate) {
let timeout;
return function () {
const context = this;
clearTimeout(timeout);
//立即执行就是事件刚开始就执行一次先。如每次滚动先执行一次,滚动结束一次。
//用timeout来判断
if (immediate) {
var callNow = !timeout;
timeout = setTimeout(() => {
//如果只是想只立即执行一次,就不用 timeout = null; 了,加了每次滚动都会执行一次,不加就只是最最开始的滚动执行一次,后面和平时一样。
timeout = null;
func.apply(context, arguments);
}, wait);
if (callNow) {
func.apply(context, arguments);
}
} else {
timeout = setTimeout(() => func.apply(context, arguments), wait);
}
};
}
container.addEventListener('scroll', debounce(handleScroll, 1000,true))
复制代码
返回值
拿到的返回值都是上次定时任务的返回值,初始的返回值都是undefined
,这个使用场景应该比较苛刻吧
var container = document.getElementsByClassName("container")[0];
function handleScroll(e) {
console.log("事件对象", e);
console.log("this", this);
console.log("滚动了");
return 1;
}
container.addEventListener("scroll", debounce(handleScroll, 1000, false));
// container.addEventListener("scroll", debounce(handleScroll, 1000, true));
function debounce(func, wait, immediate) {
let timeout, res;
return function () {
const context = this;
clearTimeout(timeout);
//立即执行就是刚开始就执行一次先。后面的按规矩来
//用timeout来判断
if (immediate) {
var callNow = !timeout;
timeout = setTimeout(() => {
timeout = null;
func.apply(context, arguments);
}, wait);
if (callNow) {
func.apply(context, arguments);
}
} else {
timeout = setTimeout(() => {
res = func.apply(context, arguments);
}, wait);
}
console.log({ res });
return res;
};
}
复制代码
取消防抖
function debounce(func,wait) {
let timeout
const debounced = function() {
clearTimeout(timeout)
timeout = setTimeout(()=>func.apply(this,argument),wait)
}
debounced.cancel=()=>{
clearTimeout(timeout)
}
return debounced
}
复制代码
underscore的debounce
为了理解这个例子,我举了个例子
设wait为1s
开始触发一次,0.4s触发一次,那么应该1.4s执行任务
在debounced里,只会在最开始执行一次later,每次触发都会更新previous
在later里进行比较,如果wait大于now-previous(两次触发间隔),也就是还达不到wait的时间,那需要重新计时,达到了就直接执行。
初始化时later会在1s后触发判断,由于0.4s触发更新了previous,导致两次触发间隔达不到wait,需要延时,以0.4s为基准,按理是1.4s后再判断,此时是时间1s,所以要在0.4s后判断,1s-(当前-之前=0.6)=0.4.
差不多是这样。
import restArguments from './restArguments.js';
import now from './now.js';
// When a sequence of calls of the returned function ends, the argument
// function is triggered. The end of a sequence is defined by the `wait`
// parameter. If `immediate` is passed, the argument function will be
// triggered at the beginning of the sequence instead of at the end.
export default function debounce(func, wait, immediate) {
var timeout, previous, args, result, context;
var later = function() {
var passed = now() - previous;
if (wait > passed) {
timeout = setTimeout(later, wait - passed);
} else {
timeout = null;
if (!immediate) result = func.apply(context, args);
// This check is needed because `func` can recursively invoke `debounced`.
if (!timeout) args = context = null;
}
};
var debounced = restArguments(function(_args) {
context = this;
args = _args;
previous = now();
if (!timeout) {
timeout = setTimeout(later, wait);
if (immediate) result = func.apply(context, args);
}
return result;
});
debounced.cancel = function() {
clearTimeout(timeout);
timeout = args = context = null;
};
return debounced;
}
复制代码