防抖与节流是js中最常用的优化技术,因为用户在操作页面的时候常常会有误操作与多余操作,因此使用防抖和节流的技术可以提升性能和用户体验。
顾名思义,是防止用户因为意外操作导致函数立刻执行,破坏了用户体验,应用场景有窗口调整、实时输入查询、输入检测等。
防抖的核心是延迟执行,也就是在一次操作后,等待一段时间delay
,才会执行目标函数。如果在 delay
内再次触发事件,则重新计时。
按照以上的思路尝试编写一个防抖函数。
既然是延迟执行,首先想到的是定时器,定时器被调用后,经过一段时间才会真正执行被传入的函数。
接下来考虑,如何实现等待时间内再次触发事件重新计时呢?清除之前的定时器并且再次调用定时器。
<button id="button">hellobutton>
<script>
const btn = document.querySelector('#button');
// 需要执行的函数
const sayHello = () => {
console.log('hello');
};
let timer;
// 防抖函数
const debounce = (func) => {
clearTimeout(timer);
timer = setTimeout(() => {
func();
}, 1000);
};
// 绑定到按钮上
btn.addEventListener('click', () => debounce(sayHello))
script>
通过这种方式我们已经实现了防抖的基础功能,但这种写法依然存在缺陷。
防抖函数无法复用:所有的debounce()方法共用一个timer变量,那么如何让每个调用都维护自己独立的变量呢?使用闭包。
无法获取到this对象:我们期望this对象是调用者,在上面的例子中,我们希望this是button元素,但是我们的button实际指向了window(严格模式下指向undefined)。
尝试优化
<button id="button">hellobutton>
<button id="button2">hello2button>
<script>
const btn = document.querySelector('#button');
const btn2 = document.querySelector('#button2');
// 需要执行的函数
const sayHello = function() {
console.log(this);
console.log('hello')
};
// 防抖函数
const debounce = (func, delay = 1000) => {
// 使用闭包、让每个函数维护自己的timer变量
let timer;
// 这里不能使用箭头函数,否则this会指向window(严格模式undefined)
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => {
func.apply(this, args);
}, delay);
};
};
// 绑定到按钮上
btn.addEventListener('click', debounce(sayHello))
btn2.addEventListener('click', debounce(function(){console.log(this)}))
script>
接下来我们想继续进行优化,第一次按的时候要等待delay,这会导致用户体验不佳,我们想让他首次触发就立即执行。
那么应该新建一个变量,当他为true的时候就执行,再设置一个定时器,delay后将变量设置为true
// 立即执行防抖函数
function debounceImmediate (func, delay = 1000) {
let timer;
let callNow = true;
return function (...args) {
clearTimeout(timer);
if (callNow) {
func.apply(this, args);
callNow = false;
}
timer = setTimeout(() => { callNow = true; }, delay);
};
}
顾名思义,是防止用户因为频繁操作导致函数频繁调用,影响了性能,应用场景有滚动事件(scroll
)、鼠标移动(mousemove
)、动画渲染等。
节流的本质是固定频率执行,无论触发多频繁,函数在 interval
时间间隔内最多执行一次。
有了写节流的经验,我们可以直接写。
通过闭包的形式引用外部变量pre,每次调用函数时生成一个Date()对象记录now当前的时间,如果now-pre的差值大于定义的时间间隔,回调函数才会被调用,并且将当前的时间作为下次间隔开始的时间,等待下次调用。
const throttle = (func,interval = 1000) => {
let pre = 0
return function(...args){
const now = new Date();
if(now - pre > interval){
func.apply(this,args);
pre = now;
}
}
}