AboutFE
AboutFE copied to clipboard
35、防抖和节流高阶
函数防抖(debounce)
当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时。如下图,持续触发scroll事件时,并不执行handle函数,当1000毫秒内没有触发scroll事件时,才会延时触发scroll事件。
将几次操作合并为一此操作进行。原理是维护一个计时器,规定在delay时间后触发函数,但是在delay时间内再次触发的话,就会取消之前的计时器而重新设置。这样一来,只有最后一次操作能被触发。
function debounce(fn, ms) {
let timer;
return function(...args) {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
fn(...args)
timer = null;
}, ms);
}
}
function debounce(fn, wait) {
var timeout = null;
return function() {
if(timeout !== null)
clearTimeout(timeout);
timeout = setTimeout(fn, wait);
}
}
// 处理函数
function handle() {
console.log(Math.random());
}
// 滚动事件
window.addEventListener('scroll', debounce(handle, 1000));
函数节流(throttle)
当持续触发事件时,保证一定时间段内只调用一次事件处理函数。节流通俗解释就比如我们水龙头放水,阀门一打开,水哗哗的往下流,秉着勤俭节约的优良传统美德,我们要把水龙头关小点,最好是如我们心意按照一定规律在某个时间间隔内一滴一滴的往下滴。持续触发scroll事件时,并不立即执行handle函数,每隔1000毫秒才会执行一次handle函数
使得一定时间内只触发一次函数。原理是通过判断是否到达一定时间来触发函数。
// 时间戳 + 定时器方式:
var throttle = function(func, delay) {
var timer = null;
var startTime = Date.now();
return function() {
var curTime = Date.now();
var remaining = delay - (curTime - startTime);
var context = this;
var args = arguments;
clearTimeout(timer);
if (remaining <= 0) {
func.apply(context, args);
startTime = Date.now();
} else {
timer = setTimeout(func, remaining);
}
}
}
function handle() {
console.log(Math.random());
}
window.addEventListener('scroll', throttle(handle, 1000));
区别和场景
函数节流不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数,而函数防抖只是在最后一次事件后才触发一次函数。 比如在页面的无限加载场景下,我们需要用户在滚动页面时,每隔一段时间发一次 Ajax 请求,而不是在用户停下滚动页面操作时才去请求数据。这样的场景,就适合用节流技术来实现。
防抖:任务频繁触发的情况下,只有任务触发的间隔超过指定间隔的时候,任务才会执行。在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。个人理解 函数防抖就是法师发技能的时候要读条,技能读条没完再按技能就会重新读条。
在触发点击事件后,如果用户再次点击了,我们会清空之前的定时器,重新生成一个定时器。意思就是:这件事儿需要等待,如果你反复催促,我就重新计时!
防抖场景:
- search搜索联想,用户在不断输入值时,用防抖来节约请求资源。有个输入框,输入之后会调用接口,获取联想词。但是,因为频繁调用接口不太好,所以我们在代码中使用防抖功能,只有在用户输入完毕的一段时间后,才会调用接口,出现联想词。
- window触发resize的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次
节流:指定时间间隔内只会执行一次任务。规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。个人理解 函数节流就是fps游戏的射速,就算一直按着鼠标射击,也只会在规定射速内射出子弹。
懒加载要监听计算滚动条的位置,使用节流按一定时间的频率获取。 用户点击提交按钮,假设我们知道接口大致的返回时间的情况下,我们使用节流,只允许一定时间内点击一次。
节流场景
- 鼠标不断点击触发,mousedown(单位时间内只触发一次)
- 监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断
QA
- 什么是防抖、节流,分别解释一下?
- 在白纸上手写一个防抖or节流函数,自己任选(限时4分钟)
- react hooks有了解吗?上机实现一个useDebounce、useThrottle
- tyepscript有了解吗?用ts再来写一遍
function useDebounce(fn, delay) {
const { current } = useRef({});
function f(...args) {
if (current.timer) {
clearTimeout(current.timer);
}
current.timer = setTimeout(fn.bind(undefined, ...args), delay);
}
return f;
}
export function useThrottle(fn, delay) {
const { current } = useRef({});
function f(...args) {
if (!current.timer) {
current.timer = setTimeout(() => {
delete current.timer;
}, delay);
fn(...args);
}
}
return f;
}
防抖 :
const deb = (fn, delay, immediate) => {
let timer = null
return function() {
const context = this
timer && clearTimeout(timer)
if (immediate) {
!timer && fn.apply(context, arguments)
}
timer = setTimeout(() => {
fn.apply(context, arguments)
}, delay)
}
}
节流
const throttle = (fn, delay = 2000) => {
let timer = null
let startTime = new Date()
return function() {
const context = this
let currentTime = new Date()
clearTimeout(timer)
if (currentTime - startTime >= delay) {
fn.apply(context, arguments)
startTime = currentTime
} else {
//让方法在脱离事件后也能执行一次
timer = setTimeout(() => {
fn.apply(context, arguments)
}, delay)
}
}
}