对应场景:用户多次点击按钮造成表单重复提交
绑定拖拽事件时,每当元素移动一点便触发了大量的回调函数,导致浏览器被卡死
两者都是通过减少实际逻辑处理过程的执行来提高事件处理函数运行性能的手段,并没有实质上减少事件的触发次数。整体是用来优化前端代码
函数防抖(debounce)
原理:事件被触发,执行回调,如果单位时间内又触发,则重新计时
重点在于空闲的间隔时间
应用:
- 给按钮加函数防抖防止表单多次提交。
- 对于输入框连续输入进行AJAX验证时,用函数防抖能有效减少请求次数。
- 判断 scroll 是否滑到底部, 滚动事件 + 函数防抖
- 实时搜索(keyup)
- 拖拽(mousemove)
源代码:把多个函数放在一个函数里调用,隔一定的时间执行一次
// debounce函数用来包裹我们的事件
function debounce(fn, delay) {
// 持久化一个定时器 timer
let timer = null;
// 闭包函数可以访问 timer
return function() {
// 通过 'this' 和 'arguments'
// 获得函数的作用域和参数
let context = this;
let args = arguments;
// 如果事件被触发,清除 timer 并重新开始计时
clearTimeout(timer);
timer = setTimeout(function() {
fn.apply(context, args);
}, delay);
}
}
调用方法:
// 当用户滚动时函数会被调用
function foo() {
console.log('You are scrolling!');
}
// 在事件触发的两秒后,我们包裹在debounce中的函数才会被触发
let elem = document.getElementById('container');
elem.addEventListener('scroll', debounce(foo, 2000));
函数节流(throttle)
原理:相当于一个定时器,当触发一个时间时,先setTimeout让这个事件延迟一会执行,如果这个时间间隔内又触发了事件,就清除掉定时器,重新setTimeout一个定时器,延迟再执行
重点在于连续的执行间隔时间
应用:
- 窗口调整(resize)
- 页面滚动(scroll)
- 抢购疯狂点击(mousedown)
源代码:
方法一:普通方法:将定时器ID存为函数的一个属性
function throttle(method, context) {
clearTimeout(methor.tId);
method.tId = setTimeout(function(){
method.call(context);
}, 100);
}
调用时:
window.onresize = function(){
throttle(myFunc);
}
方法二:impress的方法
用闭包的方法形成一个私有作用域存放定时器变量timer
var throttle = function(fn, delay){
var timer = null;
return function(){
var context = this, args = arguments;
clearTimeout(timer);
timer = setTimeout(function(){
fn.apply(context, args);
}, delay);
};
};
调用形式:
window.onresize = throttle(myFunc, 100);
- 两种方法的优缺点:
普通方法优势:把上下文变量当做函数参数,直接可以定制执行函数的this变量
impress优势:把延迟时间当做变量(当然,前一个函数很容易做这个拓展),而且使用闭包代码结构会更优,且易于拓展定制其他私有变量
缺点就是虽然使用apply把调用throttle时的this上下文传给执行函数,但毕竟不够灵活。
方法三、对函数节流的优化
在优化的函数里设置了第三个参数,即必然触发执行的时间间隔
var throttleV2 = function(fn, delay, mustRunDelay){
var timer = null;
var t_start;
return function(){
var context = this, args = arguments, t_curr = +new Date();
clearTimeout(timer);
if(!t_start){
t_start = t_curr;
}
if(t_curr - t_start >= mustRunDelay){
fn.apply(context, args);
t_start = t_curr;
}
else {
timer = setTimeout(function(){
fn.apply(context, args);
}, delay);
}
};
};
调用方法:
window.onresize = throttleV2(myFunc, 50, 100);
即在50ms的间隔内连续触发调用,后一个调用会把前一个调用的等待处理掉,但是每隔100ms至少执行一次