本篇文章中的防抖和节流方法均已添加进Jsutl,该库为一个功能性的函数库,欢迎大家引用及Star或者提出宝贵的意见。
防抖及节流的个人理解
函数防抖和函数节流在面试中都是老生常谈的问题,那么今天就让我来说说自己对这两个概念的理解吧。
函数防抖的概念和例子
函数防抖(debounce)
所谓的防抖,实际上就是在触发事件n秒后,才执行回调函数,如果在这n秒内又重新触发了事件,则重新计时
举个栗子:
// 模拟接口请求
const request = (val) => {
setTimeout(() => {
console.log('request:', val);
}, 100)
}
let $normalInput = document.getElementById("normalInput")
$normalInput.addEventListener("input", (e) => {
request(e.target.value)
})
上面展示的是我们正常情况下在input框内输入内容后请求接口的行为,可以看到每当我们输入一个内容就会请求一次接口。这样无疑照成了资源的浪费,加大了服务器的压力。
这个时候我们是不是可以考虑,在用户输入一个完整的内容后,再请求服务器资源呢?
答案是:当然可以!防抖(debounce)可以完美的解决这一痛点
上案例:
// 模拟接口请求
const request = (val) => {
setTimeout(() => {
console.log('request:', val);
}, 100)
}
const debounceFn = function (fn, delay, immediate) {
let timer;
return (args) => {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
fn(args);
}, delay);
};
};
let $debounceInput = document.getElementById("debounceInput")
let debounceInput = debounceFn(request, 1000)
$debounceInput.addEventListener("input", (e) => {
debounceInput(e.target.value)
})
该函数为非立即执行版本,可以看到具体效果如下:
可以看到当我们频繁输入的时候,并没有发送请求,直到停止输入后,等待我们设置的间隔时间后再执行函数。
既然有非立即执行版本,那么也就意味着有立即执行版本,代码如下:
let num = 0
// 模拟接口请求
const request = () => {
num++
console.log(num);
}
// 立即执行版本
const debounceFn = function (fn, delay) {
let timer;
return (args) => {
// 每次执行防抖函数都清除定时器
if (timer) clearTimeout(timer);
// 判断防抖函数中的定时器是否在等待执行的时间间隔内
const callNow = !timer
timer = setTimeout(function () {
// 等待间隔时间后将定时器id置为null
timer = null
}, delay);
// 如果没有设置定时器就立即执行
if(callNow) {
fn(args)
}
};
};
let $debounceButton = document.getElementById('debounceButton')
let debounceButtonFn = debounceFn(request, 1500)
$debounceButton.addEventListener('click', (e) =>{
debounceButtonFn()
})
此方法适用于按钮点击时,可以看到只有我们第一次点击按钮时才触发了打印事件,当我们在间隔时间内反复的触发事件时,是不会触发打印事件的。
试着将非立即执行和立即执行版本合并
该防抖函数存放在Jsutil,欢迎访问使用。
/**
* @desc 函数防抖
* @param fn 执行函数
* @param delay 延迟执行的毫秒数
* @param immediate 是否立即执行
*/
const _Debounce = (fn, delay, immediate) => {
let timer;
return (args) => {
if (timer)
clearTimeout(timer);
// 立即执行
if (immediate) {
const callNow = !timer;
timer = setTimeout(() => {
timer = null;
}, delay);
if (callNow) {
fn(args);
}
}
else {
timer = setTimeout(() => {
fn(args);
}, delay);
}
};
};
个人理解: 函数的防抖就好比游戏中的技能施法读条,等到读条结束后就释放技能,但在读条的过程中重复释放技能的话,又会重新进行读条。
函数节流的概念和例子
函数节流(throttle)
所谓的节流,保证在一定的间隔时间内只触发一次函数的执行。
函数节流一般分为两种类型:时间戳版本和定时器版本
这两种版本的区别在于,时间戳版本的函数触发是在设定时间开始的时候,定时器版本的函数触发时在设定时间结束的时候。
话不多收,直接上代码!
时间戳版本
在持续触发函数的情况下,立即执行函数,并且每隔设定的间隔时间执行一次。
let num = 0;
const request = () => {
num++;
console.log(num);
};
const throttle = (fn, delay = 0) => {
let time = 0;
return (val) => {
let now = Date.now();
if (now - time >= delay) {
fn(val);
time = now;
}
};
};
const $input = document.querySelector('#input')
const $button = document.querySelector('#button')
$button.onclick = throttle(() => {
request()
}, 2000)
定时器版本
在持续触发函数的情况下,并不会立即执行,而是等待设定的时间间隔节后在执行,因为这个原因,当我们停止触发事件后,函数还是会执行一次。
let num = 0;
const request = () => {
num++;
console.log(num);
};
const throttle = (fn, delay = 0) => {
let time = 0;
return (val) => {
if (!time) {
time = setTimeout(() => {
fn(val)
time = null
}, delay)
}
};
};
const $input = document.querySelector('#input')
const $button = document.querySelector('#button')
$button.onclick = throttle(() => {
request()
}, 2000)
和防抖一样,我们也可以试着将时间戳版本和定时器版本结合
该节流函数存放在Jsutil,欢迎访问使用。
/**
* @desc 函数节流
* 连续触发函数,但是在n秒中只执行一次,可以稀释函数执行的频率
* 好比技能cd 释放技能后要等待冷却时间
* @param fn 执行函数
* @param delay 延迟执行的毫秒数 默认为500ms
* @param type 1 表示时间戳类型(立即触发) 2 表示定时器(到时间再触发) 3 时间戳+定时器版: 实现第一次触发可以立即响应,结束触发后也能有响应
*/
const _Throttle = (fn, delay = 500, type = 1) => {
let previous = 0, timer;
return (val) => {
if (type === 1 || type === 3) {
let now = Date.now();
// 当前时间-先前时间 > 延迟时间
if (now - previous > delay) {
fn(val);
previous = now;
}
}
if (type === 2 || type === 3) {
if (!timer) {
timer = setTimeout(() => {
clearTimeout(timer);
timer = null;
fn(val);
}, delay);
}
}
};
};
个人理解: 函数的节流就好比游戏中技能CD,在单位时间内只能施法一次。
欢迎大家指正