前言
防抖
和 节流
很多人特别容易把概念混倄,今天,一篇文章教你彻底理清其中的概念及应用场景。
先说共同点
防抖 和 节流 函数都是为了 限制函数的执行频次,从而优化函数触发频率过高导致的响应速度跟不上触发频率,导致出现延迟、卡顿的现象以及其他错误。 是常见的前端性能优化技巧。
但应用场景不同
防抖
防抖通常用来处理一些高频触发的事件,比如用户连续点击按钮、输入框频繁输入。
当用户点击按钮过快,导致重复点击,多次调用接口;或者输入框的文字一变化就调用接口,导致多次调用接口。
此时,我们肯定是希望哪怕多次点击按钮,也只调用一次接口;也不希望输入框每输入一个字母就发送一次请求,那样页面就会频繁地发送请求,极大的浪费资源。
使用防抖可以在用户输入完毕后再发送请求,或者避免重复点击按钮重复请求,减少函数的执行次数及接口调用,避免频繁更新UI或者发送请求造成性能浪费。
简单点来说:防抖 就是最后一次说了算。其核心就是:在某段时间内,不管触发了多少次回调,都只认最后一次。
四个字概括:等你到底
// 写一个简单的防抖函数
/**
* @param fn:需要用来防抖的方法,是一个回调
* @param wait: 时间间隔
* 对于短时间内连续触发的事件,防抖可以用来在某个时间内(wait),事件处理函数只执行最后一次
*/
export const debounce = (fn: any, wait: num) => {
let timer: any = null
return () => {
if(timer) {
clearTimerout(timer)
}
timer = setTimeout(fn, wait)
}
}
节流
节流: 是指对于连续触发的事件,每隔一段时间内执行一次,节流里面涉及的时间主要指时间执行的间隔时间。
通常用于一些高频率的事件中,比如 鼠标滚动、窗口resize、滚动条滑动 等操作。在这种情况下,我们希望这些事件能够以一定的频率触发(要触发多次),但不是每次事件都触发,节流函数可以规定在一定时间间隔中,该方法只执行一次。这样可以减少函数的执行次数,优化性能。
举个简单例子,当用户拖动滚动条时,如果每次滚动都触发事件,页面就会非常频繁的更新UI,造成卡顿。使用节流函数可以限制触发频率,提升页面流畅度。
简单点来说:节流 就是第一次说了算。其核心就是:在某段时间内,不管触发了多少次回调,都只认第一次。
时间戳节流
// 时间戳节流实现
function throttle(fn: any, wait: number) {
let prev = 0;
return function(...args) {
const now = Date.now();
if (now - prev >= delay) {
fn(...args);
prev = now;
}
};
}
上面的节流函数中,在每次事件触发时,判断当前时间和上一次触发事件的间隔是否超过指定的时间间隔 wait
,如果超过了,则执行时间处理函数 fn
,并更新上一次触发事件为当前时间。
写一个方法测试上面的节流函数。
// 测试
function log() {
console.log('hello world');
}
const throttledLog = throttle(log, 3000);
setInterval(throttledLog, 50);
上面的代码中,本来是 50毫秒调用一次 throttleLog
,但由于设置了 3000 毫秒的节流时间间隔,所以,实际上,log 函数只会每隔 3000 毫秒 执行一次。
使用上面的 时间戳节流,还可以有另一种写法:限制在一定时间内触发事件次数:即我们自定义在一定时间间隔内,调用次数,做个限制,不过这里不做多说。
定时器节流
// 定时器节流
export const throttle = (fn: any, wait: number) => {
let timer: any = null;
return function(...args) {
if(!timer) {
timer = setTimeout(function() => {
fn(...args);
timer = null
}, wait)
}
}
}
1. 节流中,timer 为什么要 等于 null?
首先我们要知道一件事:定时器是有返回值的,返回值是当前定时器的 ID号,clearTimeout(timer) ,会清除该定时器函数,但不会改变 id 值。从下图我们可以看出,使用clearTimeout并不会改变 timer 的ID号,但可以关闭定时器。
如果我们不将 timer 设置为 null,而是直接设置新的定时器 ID,那么就会无法清除之前的定时器,会导致多个定时器同时执行,达不到节流的效果。所以在每次执行定时器函数之前,将 timer 设置为 null,确保下次事件触发时能正确清除之前的定时器。