函数防抖及应用场景总结
环境初始化
<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="IE=edge, chrome=1">
<title>debounce</title>
<style>
#container{
width: 100%; height: 200px; line-height: 200px; text-align: center; color: #fff; background-color: #444; font-size: 30px;
}
</style>
</head>
<body>
<div id="container"></div>
<button id="button">cancel</button>
<script src="debounce.js"></script>
</body>
</html>
函数防抖第一版
// 环境初始化
let count = 1;
let container = document.getElementById('container');
function handleUserAction(e){
console.log(this, e);
container.innerHTML = count++;
// 如果这个函数有返回值的话
return {
status: true,
msg: 'execute success'
}
}
// 滑动的时候会不断地触发页面的move函数,性能损耗巨大
container.onmousemove = handleUserAction;
/*
* 防抖原理:你尽管触发事件,但是我一定在事件触发 n 秒后才执行,
* 如果你在一个事件触发的 n 秒内又触发了这个事件,
* 那我就以新的事件的时间为准,n 秒后才执行!!!
* */
// TODO: 防抖就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。
// v1. 防抖第一版
function debounce(fn, wait) {
let timeout;
return function () {
clearTimeout(timeout);
timeout = setTimeout(fn, wait);
}
}
container.onmousemove = debounce(handleUserAction, 1000);
// TODO: 如果不使用debounce函数的时候,this就是container DOM元素;当使用之后,this指向window?
如果不使用debounce函数的时候,this就是container DOM元素;当使用之后,this指向window?(已解决)
// v2. 防抖第二版
function debounce(fn, wait) {
let timeout;
return function () {
let context = this;
clearTimeout(timeout);
timeout = setTimeout(function () {
fn.apply(context);
}, wait);
}
}
如果不使用debounce函数的时候,handleUserAction的参数就是Event; 使用之后变成undefined?(已解决)
// v3: 防抖第三版
function debounce(fn, wait) {
let timeout;
return function () {
let context = this;
let args = arguments;
clearTimeout(timeout);
timeout = setTimeout(function () {
// 把当前的环境下的this和变量参数传过去
console.log(args)
// apply 也是可以直接传递一个伪数组arguments的,也可以是Array数组对象
fn.apply(context, args);
}, wait);
}
}
我不希望非要等到事件停止触发后才执行,我希望立刻执行函数,然后等到停止触发 n 秒后,才可以重新触发执行呢?(已解决)
// TODO: 立即执行版的意思是触发事件后函数会立即执行,然后 n 秒内不触发事件才能继续执行函数的效果。
// v4: 防抖第四版
function debounce(fn, wait, immediate) {
let timeout;
return function () {
let context = this;
let args = arguments;
if (timeout) {
clearTimeout(timeout);
}
// 根据传入的参数判断是否立即执行当前的事件
if (immediate) {
// 如果已经执行过的话,就不再执行, 需要在执行之前,提前存一份,防止下一句代码重新生成了一个定时器
let callNow = !timeout; // !表示取反, !!表示转换为bool值
// 开始重新生成一个全新的定时器
timeout = setTimeout(function () {
timeout = null;
}, wait);
// 如果之前就没有timeout定时器的话,就去执行
if (callNow) {
console.log('run now')
// 立即执行
fn.apply(context, args);
}
}
else {
// 默认的情况处理
timeout = setTimeout(function () {
let res = fn.apply(context, args);
console.log('get result:', res);
}, wait);
}
}
}
container.onmousemove = debounce(handleUserAction, 1000, true);
如何给我的防抖函数加上返回值呢?(已解决)
// v5: 防抖第5版
/**
* 设计一个函数防抖的函数
* @param fn
* @param wait
* @param immediate
* @return {Function}
*/
function debounce(fn, wait, immediate) {
let timeout; // 定时器对象
let result; // handleUserAction函数的执行结果
return function () {
let context = this;
let args = arguments;
if (timeout) {
clearTimeout(timeout);
}
if (immediate) {
// 立即执行的话
let callNow = !timeout;
timeout = setTimeout(function () {
timeout = null;
}, wait);
if (callNow) {
result = fn.apply(context, args);
}
}
else {
// 传统的方式的执行的话
timeout = setTimeout(function () {
// 由于这里实际上就是一个异步的执行结果,所有此时的result最后每次都会返回的是一个undefined
result = fn.apply(context, args);
}, wait);
}
// 在闭包函数的最后,返回函数执行的结果
console.log('execute result: ', result);
return result;
}
}
container.onmousemove = debounce(handleUserAction, 1000, false);
如何实现防抖的取消功能?(已解决)
// TODO: 实现防抖功能的取消?
// v6:防抖第六版,新增取消防抖的功能; 只有immediate的时候才处理返回的结果
function debounce(fn, wait, immediate) {
let timeout;
let result;
// 创建一个debounce防抖函数
let debounced = function () {
let context = this;
let args = arguments;
if (timeout) {
clearTimeout(timeout);
}
if (immediate) {
let callNow = !timeout;
timeout = setTimeout(function () {
timeout = null;
}, wait);
if (callNow) {
result = fn.apply(context, args);
}
}
else {
timeout = setTimeout(function () {
fn.apply(context, args);
}, wait);
}
return result;
};
// 防抖函数新增cancel函数
debounced.cancel = function () {
console.log('cancel finished!', timeout);
clearTimeout(timeout);
timeout = null;
};
return debounced;
}
// 1. 创建一个debounce函数
let setUserAction = debounce(handleUserAction, 10000, true);;
// 2. 设置当前的回调函数
container.onmousemove = setUserAction;
// 3. 监听按钮的取消事件(并不是取消了整个debounce,点击按钮之后,可以直接触发一次事件;每次点击就可以立即触发当前的事件,而不需要等待10s)
document.getElementById("button").addEventListener('click', function(){
console.log(setUserAction)
setUserAction.cancel();
})
Debounce防抖应用场景:
- 鼠标/触摸屏的mouseover/touchmove事件
- 页面窗口的resize事件
- 滚动条的scroll事件