函数防抖和函数节流:优化高频率执行js代码的一种手段,js中的一些事件如浏览器的resize、scroll,鼠标的mousemove、mouseover,input输入框的keypress等事件在触发时,会不断地调用绑定在事件上的回调函数,极大地浪费资源,降低前端性能。为了优化体验,需要对这类事件进行调用次数的限制。
防抖:
在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
根据函数防抖思路设计出第一版的最简单的防抖代码:
let timer; // 共同维护一个timer
const debounce = (fn, delay) => {
clearTimeout(timer);
timer = setTimeout(() => {
fn();
}, delay);
};
在上面的代码中,会出现一个问题,let timer只能在setTimeout的父级作用域中,这样才是同一个timer,并且为了方便防抖函数的调用和回调函数fn的传参问题,我们应该用闭包来解决这些问题。
function debounce (fn, delay) {
let timer;
return function (..arg) {
if (timer) {
clearTimeout(timer);
}
const _this = this;
const arg = arguments;
timer = setTimeout(fn.apply(_this, arg), delay);
};
}
节流:
每隔一段时间,只执行一次函数。
1) 使用定时器实现节流
先比较简单的实现一下节流效果,代码如下:
function throttle(fn, delay) {
let timer;
return function (...arg) {
if (timer) {
return;
}
const arg = arguments;
const _this = this;
timer = setTimeout(function () {
fn.apply(_this, arg);
timer = null; // 清空定时器
}, delay);
};
}
以上代码存在一个问题,即函数第一执行也会在delay之后,即第一次不会立即执行,那么如何让函数第一次立即执行,后续也能节流呢?
我们把上面的代码改造一下,增加一个变量用于控制是否立即执行:
function throttle(fn, delay) {
// 是否立即执行
let immediate = true;
let timerId = null;
return function (...arg) {
if (timerId) return null;
timerId = setTimeout(
() => {
fn(...arg);
immediate = false;
timerId = null;
},
immediate ? 0 : delay
);
};
}
以上是我们内部写死了立即执行,我们也可以让立即执行交给调用方控制,把这个变量暴露出去:
function throttle(fn, delay, immediate) {
let timerId = null;
return function (...arg) {
if (timerId) return null;
timerId = setTimeout(
() => {
fn(...arg);
immediate = false;
timerId = null;
},
immediate ? 0 : delay
);
};
}
调用的时候可以传对应参数:
throttle(fn, 2000, true)
进行控制是否使用第一次调用立即执行。
2)使用时间戳来实现节流
使用时间戳实现的话,我们默认当前previsious的时间为0,则第一次current - previsious 必定大于delay,所以第一次必定会立即执行,不用考虑上面的那种是否立即执行的情况。
function throttler(fn, delay) {
let previsious = 0;
return function () {
const _this = this;
const arg = arguments;
const current = new Date();
if (current - previsious > delay) {
fn.apply(_this, arg);
previsious = current;
}
};
}