Open
Description
在前端开发中,某些场景下会遇到一些频繁的事件触发,比如:
- window 的 resize、scroll
- mousedown、mousemove
- keyup、keydown
如果这些频繁的事件触发后是一些复杂的回调操作,那么就会导致性能浪费,甚至程序崩溃。
防抖和节流的出现就是为了遏制频繁触发的事件回调。
防抖
防抖的原理就是:事件触发n秒后才执行回调,如果这n秒内事件再次触发,重新计时。
防抖分为非立即执行版本和立即执行版本。
非立即执行版防抖
非立即执行防抖就是传统的防抖,事件触发n秒后才执行回调函数。
实现如下:
function debounce(fn, wait){
let timeout;
return function(){
let args = [...arguments];
//定时器存在,也就是还处于n秒内,清楚该定时器,在下面重新计时
if(timeout) clearTimeout(timeout);
timeout = setTimeout(() => {
fn.apply(this, args)
}, wait);
}
}
立即执行版防抖
立即执行防抖就是事件第一次触发时马上执行回调,然后隔n秒后才可以触发第二次,如果这n秒内事件再次触发,重新计时。
实现如下:
function debounce(fn, wait){
let timeout;
return function(){
let args = [...arguments];
//定时器存在,也就是还处于n秒内,清除该定时器,在下面重新计时
if(timeout) clearTimeout(timeout)
//定时器存在,也就是还处于n秒内,不能执行回调
let callNow = !timeout;
timeout = setTimeout(() => {
timeout = null;
}, wait)
if(callNow) fn.apply(this, args);
}
}
节流
节流的原理就是:事件频繁触发但是n秒内只会执行一次回调函数,也就是会稀释事件回调的执行频率
关于节流的实现,有两种实现方式,一种是使用时间戳,一种是设置定时器。
时间戳版节流
原理就是设立一个时间戳,当事件触发时取出当前的时间戳,然后减去之前的时间戳(最一开始值设为0),如果大于设立的时间周期,就执行函数并更新时间戳为当前的时间戳,如果小于就不执行。
实现如下:
function throttle(fn, wait){
let time = 0;
return function(){
let now = new Date();
let args = [...arguments];
if(now - time > wait){
fn.apply(this, args);
time = now;
}
}
}
由于第一次进入time默认是0,now - time
第一次都会大于规定的时间(除非传入一个特别大的时间),所以第一次事件回调立刻执行,当停止触发事件后,不会再执行回调。
定时器版节流
原理就是设立一个定时器,定时器存在时就不执行回调,当事件触发时会启动定时器,定时器到期后会把自己赋值为null。
实现如下:
function throttle(fn, wait){
let timeout;
return function(){
let args = [...arguments];
if(!timeout){
timeout = setTimeout(() => {
timeout = null;
fn.apply(this, args)
}, wait)
}
}
}
由于基于定时器,所以会有延后执行的特性,第一次事件触发时不会立即执行,事件停止触发后过一段时间还会执行一次。
需要注意的点
- 注意this的指向,如果不用apply会导致fn的this指向window。
- JS在事件处理函数中会提供事件对象event,要将这个事件对象传递给fn。
参考
JavaScript专题之跟着underscore学防抖
JavaScript专题之跟着underscore学节流
函数防抖和节流