今天,我们来复习一下JS中的防抖和节流问题,防抖和节流是性能优化中的实际应用技巧,应用场景非常广泛,同时也是前端面试的经典考题,在此,我会将防抖和节流的概念、实现思路和实际手写代码一一给大家解释清楚,如有错误还请指正,谢谢。
首先我们要知道用户可能在某个时间多次触发一个事件,这个事件可能是鼠标点击,也可能是滑动窗口,如果此时我们不加限制,就会频繁触发回调函数,这时大量的计算无疑会严重影响网页性能,所以函数节流和函数防抖应运而生。
函数防抖
什么是函数防抖?
函数防抖指的是一个事件触发函数会在设定的时间后被执行,但是如果有新的事件,则需要重新计时。本质上说,函数防抖实现了延时功能,特殊点在于此延时是由最后一个用户事件决定的。 函数防抖的效果是,当频繁触发一个事件时,比如持续按下按键输入字符,回调函数不会立刻执行,而是等待用户停止按键输入n秒后再执行。
函数防抖的场景
- 搜索联想,用户不停地输入字符时
- window触发resize时
手写函数防抖
function debounce(fn, wait){
var timer = null;
//这里使用闭包,timer一直存在于内存中,第一次之后的调用都会直接访问闭包
return function(){
if(timer !== null){
clearTimeout(timer);
}
timer = setTimeout(fn, wait);
}
}
function handle(){
console.log("this is decounce")
}
window.addEventListener('scroll', debounce(handle, 1000));
函数节流
什么是函数节流?
函数节流可以形象地理解为液压系统中的节流阀,这个函数也就是“阀”控制了回调函数执行的流量(次数)。它标准的定义是在规定的时间内,节流函数只允许执行一次,当在某个时间内多次触发该函数,则从第一次触发开始计时,到达规定时间后再执行函数。总结一下,函数节流是由第一次触发决定的,第一次触发开始计时,限定时间内后面的触发都会被忽略。
节流应用场景
- 鼠标不断的点击
- 监听滚动事件
手写函数节流
函数节流有两种方式,一个是通过定时器,另一个是使用时间戳。定时器方式和防抖类似,通过闭包保存上一次定时器的状态,如果定时器为null,则设置新的定时器,到时间则执行回调函数,并将定时器置为null。定时器的特点是延时执行,所以第一次触发事件后要等到定时器规定的时间后才会执行一次,当最后一次停止触发,还会执行一次回调。时间戳版也是通过闭包保存上一次的时间戳,然后和触发事件的当前时间戳对比,如果大于规定时间则执行回调,否则什么都不做。它的特点是第一次触发会执行,之后要等规定时间后才能再次执行回调函数。
// time stamp
var throttle = function(func, delay){
// previous time when bind to the event
var prev = Date.now();
// actual events happened are in the enclosure
return function(){
var context = this;
var args = arguments;
// current time
var now = Date.now();
if (now - prev >= delay){
// operation
func.apply(context, args);
prev = Date.now();
}
}
}
function handle(){
console.log("this is throttle");
}
window.addEventListener('scroll', throttle(handle,1000));
// timer version
var throttle2 = function(func,delay){
// define a empty timer
var timer = null;
// actual events happened are in the enclosure
return function(){
var context = this;
var args = arguments;
// if the timer is null, set the timer
if(!timer) {
timer = setTimeout(function(){
// operation
func.apply(context, args);
// once we have finished the operation, clear the timer
timer = null;
}, delay);
}
}
}
function handle2(){
console.log("this is throttle");
}
window.addEventListener('scroll',throttle2(handle2,1000));
下面这个版本是时间戳+定时器合体版,即在delay时间内,可以生成新的定时器,但是只要delay时间到了,就不会再生成定时器,而是直接执行回调。这个方案非常完美,解决了防抖太有耐心的问题,使得页面迟迟没有响应的问题得到解决。
// Advanced version: time stamp + timer
var throttle3 = function(func,delay){
// define timer and initial time stamp
var timer = null;
var startTime = Date.now();
// use closure
return function(){
// get current time when the event happened
var curTime = Date.now();
var remaining = delay - (curTime - startTime);
var context = this;
var args = arguments;
clearTimeout(timer);
if(remaining <= 0){
func.apply(context,args);
startTime = Date.now();
}else{
timer = setTimeout(func, remaining);
}
}
}
function handle3(){
console.log("this is advanced throttle");
}
window.addEventListener('scroll',throttle3(handle3,1000));