在进行窗口的resize、scroll,输入框内容校验等操作时,如果事件处理函数调用的频率无限制,会加重浏览器的负担,导致用户体验非常糟糕。此时我们可以采用debounce(防抖)和throttle(节流)的方式来减少调用频率,同时又不影响实际效果。
函数防抖(debounce)
函数防抖(debounce):
当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时。如下图,持续触发scroll事件时,并不执行handle函数,当1000毫秒内没有触发scroll事件时,才会延时触发scroll事件。
在第一次触发事件时,不立即执行函数,而是给出一个期限值比如200ms,然后:
- 如果在200ms内没有再次触发滚动事件,那么就执行函数
- 如果在200ms内再次触发滚动事件,那么当前的计时取消,重新开始计时
效果:
如果短时间内大量触发同一事件,只会执行一次函数。
实现:
函数防抖的基本思想是设置一个定时器,在指定时间间隔内运行代码时清楚上一次的定时器,并设置另一个定时器,知道函数请求停止并超过时间间隔才会执行。,由于还需要一个变量来保存计时,考虑维护全局纯净,可以借助闭包来实现。
使用场景:
文本框输入搜索(连续输入时避免多次请求接口)
防抖debounce代码:
/**
* 防抖函数
* @param {Fcuntion} fn 要执行的函数
* @param {Number} delay 延迟执行的毫秒数,默认是200毫秒
* @return {Function} 防抖函数
*/
function debounce(fn, delay) {
var timer = null; //借助闭包
var delay = delay || 200;
return function() {
var args = arguments;
var that = this;
// 清楚上一次的定时器
if(timer){
clearTimeout(timer)
}
timer = setTimeout(function() {
fn.apply(that,args);
}, delay);
}
}
// 防抖函数 ES6写法
const debounce = (fn, delay=200) => {
let timer = null; //借助闭包
return function (...args) {
if(timer){
clearTimeout(timer)
}
timer = setTimeout(() => {
fn.apply(this,args);
}, delay);
}
}
// 处理函数
function handle(str){
console.log('write---',str,Date.now());
}
// 监听input输入change事件
// 注意:如果防抖的函数不需要参数的话,可以使用第一种写法防抖,如果需要防抖的函数还需要传入参数,请使用第二种写法。
//防抖第一种写法:
$('input').on('input',debounce(handle,2000));
//防抖第二种写法:
var debounced = debounce(handle,2000);
$('input').on('input',function(){
debounced($(this).val());
});
当持续触发scroll事件时,事件处理函数handle只在停止滚动1000毫秒之后才会调用一次,也就是说在持续触发scroll事件的过程中,事件处理函数handle一直没有执行。
函数节流(throttle)
函数节流(throttle):
当持续触发事件时,保证一定时间段内只调用一次事件处理函数。规定在一个单位时间内,只能触发一次函数,如果这个单位时间内触发多次函数,只有一次生效; 典型的案例就是鼠标不断点击触发,规定在n秒内多次点击只有一次生效。
效果:
如果短时间内大量触发同一事件,那么在函数执行一次之后,该函数在指定的时间期限内不再工作,直至过了这段时间才重新生效。
实现原理:
其原理是用时间戳来判断是否已到回调该执行时间,记录上次执行的时间戳,然后每次触发 scroll 事件执行回调,回调中判断当前时间戳距离上次执行时间戳的间隔是否已经到达 规定时间段,如果是,则执行,并更新上次执行的时间戳,
使用场景
resize、scroll、mousemove等事件触发监听
节流throttle代码:
/**
* 配置节流函数
* @param {Function} fn 要执行的函数
* @param {Number} delay 延迟执行的毫秒数,默认是200毫秒
* @return {Function} 节流函数
*/
function throttle(fn, delay) {
// 上一次函数触发时间
var lastTime;
var timer = null;
var delay = delay || 200;
return function() {
// 记录当前函数触发的时间
var nowTime = Date.now();
var context = this;
var args = arguments;
if (lastTime && nowTime - lastTime < delay) {
// 如果上一次函数触发时间存在,并且现在触发时间与上一次触发时间差值小鱼delay
// 说明触发时间太短,还没有到达规定的delay时间
// 所以先清除上一次的定时器,重新设置延迟执行
clearTimeout(timer);
timer = setTimeout(function(){
// 记录上一次函数触发的时间
lastTime = nowTime;
fn.apply(context, args);
}, delay);
} else {
lastTime = nowTime;
fn.apply(context, args);
}
}
}
// 节流函数 ES6写法
const throttle = (fn, delay=200) => {
// 上一次函数触发时间
let lastTime;
let timer = null;
// let delay = delay || 200;
return function (...args) {
// 记录当前函数触发的时间
let nowTime = Date.now();
if (lastTime && nowTime - lastTime < delay) {
// 如果上一次函数触发时间存在,并且现在触发时间与上一次触发时间差值小鱼delay
// 说明触发时间太短,还没有到达规定的delay时间
// 所以先清除上一次的定时器,重新设置延迟执行
clearTimeout(timer);
timer = setTimeout(() => {
// 记录上一次函数触发的时间
lastTime = nowTime;
fn.apply(this, args);
}, delay);
} else {
lastTime = nowTime;
fn.apply(this, args);
}
}
}
// 处理函数
function handle(id){
console.log('aa---',id,Date.now());
}
// 监听window的scroll事件
// 注意:如果节流的函数不需要参数的话,可以使用第一种写法节流,如果需要节流的函数还需要传入参数,请使用第二种写法。
//防抖第一种写法:
$(window).on('scroll',throttle(handle, 3000));
//防抖第二种写法:
var throttled = throttle(aa,2000);
$(window).on('scroll', function(){
throttled(99)
});
总结
函数防抖(debounce):将几次操作合并为一此操作进行。原理是维护一个计时器,规定在delay时间后触发函数,但是在delay时间内再次触发的话,就会取消之前的计时器而重新设置。这样一来,只有最后一次操作能被触发。
函数节流(throttle):使得一定时间内只触发一次函数。原理是通过判断是否到达一定时间来触发函数。
区别: 函数节流不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数,而函数防抖只是在最后一次事件后才触发一次函数。 比如在页面的无限加载场景下,我们需要用户在滚动页面时,每隔一段时间发一次 Ajax 请求,而不是在用户停下滚动页面操作时才去请求数据。这样的场景,就适合用节流技术来实现。