性能优化——防抖和节流
性能优化——防抖和节流
前言
在连续触发的事件中,事件处理函数的频繁调用会加重浏览器或服务器的性能负担导致用户体验糟糕,有哪些连续触发的事件呢 ?
比如,浏览器滚动条的滚动事件、浏览器窗口调节的resize事件、输入框内容校验以及在移动端的touchmove事件等
<div id="content"></div>
<script>
let num = 1;
let content = document.getElementById('content');
function count() {
content.innerHTML = num++;
};
content.onmousemove = count;
</script>
所以,我们将采用防抖函数(debounce )和节流函数(throttle)来限制事件处理函数的调用频率
一、防抖(debounce)
防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了该事件,则会重新计算函数执行时间
<div class="container">0</div>
let box = document.querySelector(".container");
function handleMousemove() {
box.innerText++;
}
let action = debounce(handleMousemove);
box.addEventListener("mousemove", action);
//防抖函数
function debounce(fn, wait = 500, immediate = true) {
//2.设置时间戳,使用setTimeout让返回函数延迟执行
let timer, result;
//1.直接返回传入的函数,并将返回函数的this绑定为使用位置的this
return function (...args) {
//3.timer存在时,将定时器中函数清除
if (timer) {
clearTimeout(timer);
}
if (immediate) {
//4.1 立即执行返回函数
if (!timer) {
result = fn.apply(this, args);
}
timer = setTimeout(() => {
timer = null;
}, wait);
} else {
//4.2 非立即执行返回函数
timer = setTimeout(() => {
fn.apply(this, args);
}, wait);
}
//5.立即执行时返回函数的返回值
return result;
};
}
原理解析:
- 防抖函数作用,对传入的函数进行延时包装后返回
- setTimeout在前一次未执行完前,第二次次触发将会覆盖掉前面的定时器,执行第二次的功能
- 前一次由于异步加延时还未执行完,使用clearTimeout清除前面定时器,取消上次的fn功能
- 为保持fn内部this的指向,使用apply改变this指向
- fn传入为函数,不是函数的调用
function debounce(fn, wait = 500, immediate = true) {
let timer, result;
return function (...args) {
if (timer) {
clearTimeout(timer);
}
if (immediate) {
if (!timer) {
result = fn.apply(this, args);
}
timer = setTimeout(() => {
timer = null;
}, wait);
} else {
timer = setTimeout(() => {
fn.apply(this, args);
}, wait);
}
return result;
};
}
二、节流(throttle)
节流, 指连续触发事件但是在 n 秒中只执行一次函数。
- 节流会稀释函数的执行频率
- 在持续触发事件的过程中,函数会立即执行,并且每n秒执行一次
<div class="container">0</div>
let box = document.querySelector(".container");
function handleMousemove() {
box.innerText++;
}
let action = throttle(handleMousemove);
box.addEventListener("mousemove", action);
//节流函数
function throttle(fn, wait = 1000) {
let timer;
return function (...args) {
if (!timer) {
timer = setTimeout(() => {
fn.apply(this, args);
timer = null;
//到达指定时间将定时器清除,让其能够再次进入执行fn
}, wait);
}
};
}
参考文章