函数防抖与节流
为什么要对函数进行防抖与节流?
在进行窗口的resize、scroll、mousemove,输入框内容校验等操作时,如果这些高频事件的处理函数调用的频率无限制,会加重浏览器的负担,导致用户体验非常糟糕。
此时我们可以采用debounce(防抖)和throttle(节流)的方式来减少调用频率,同时又不影响实际效果。
防抖
函数防抖是指触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间
防抖的实现上有两种方式:非立即执行、立即执行
非立即执行版
非立即执行版: 触发高频事件后过n秒执行函数fn,如果n秒内高频事件再次被触发,则重新计算时间
function debounce1(fn, wait) {
let timeout;
return function () {
if (timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(function () {
fn.apply(this, arguments);
console.log("可以再次触发了");
}, wait);
}
}
立即执行版
立即执行版: 触发高频事件后立即执行函数fn,过n秒后触发高频事件可以立即执行函数fn,如果n秒内高频事件再次被触发,则重新计算时间
function debounce2(fn, wait) {
let timeout;
return function () {
if (timeout) {
clearTimeout(timeout);
}
let callnow = !timeout;
timeout = setTimeout(function () {
timeout = null;
console.log("可以再次触发了");
}, wait);
if (callnow) {
fn.apply(this, arguments);
}
}
}
综合版
将两种实现结合在一起可以得到综合版的防抖。
/**
** @desc 函数防抖
** @param fn 函数
** @param wait 延迟执行毫秒数
** @param immediate true 表立即执行,false 表非立即执行
*/
function debounce(fn, wait, immediate) {
let timeout;
return function () {
let context = this;
let args = arguments;
if (timeout) {
clearTimeout(timeout);
}
if (immediate) {
let callnow = !timeout;
timeout = setTimeout(function () {
timeout = null;
}, wait);
if (callnow) {
fn.apply(context, args);
console.log("可以再次触发了");
}
} else {
timeout = setTimeout(function () {
fn.apply(context, args);
console.log("可以再次触发了");
}, wait);
}
}
}
节流
函数节流:当连续触发事件时,在 n 秒中只执行一次函数。节流会稀释函数的执行频率。
函数节流也有两种实现方式:时间戳版、定时器版
时间戳版
时间戳版: 在持续触发事件的过程中,函数会立即执行,并且每 1s 执行一次。
function throttle1(fn, wait) {
let previous = 0;
return function () {
let context = this;
let args = arguments;
let now = new Date();
if (now - previous > wait) {
fn.apply(context, args);
previous = now;
}
}
}
定时器版
定时器版: 在持续触发事件的过程中,函数不会立即执行,并且每 1s 执行一次,在停止触发事件后,函数还会再执行一次。
function throttle2(fn, wait) {
let timeout;
return function () {
let context = this;
let args = arguments;
if (!timeout) {
timeout = setTimeout(() => {
timeout = null;
fn.apply(context, args);
}, wait);
}
}
}
综合版
将两种实现结合在一起可以得到综合版的节流。
/**
* @desc 函数节流
* @param fn 函数
* @param wait 延迟执行毫秒数
* @param type "timestamp" 表时间戳版,"timeout" 表定时器版
*/
function throttle(fn, wait, type) {
if (type === "timestamp") {
var previous = 0;
} else if (type === "timeout") {
var timeout;
}
return function () {
let context = this;
let args = arguments;
if (type === "timestamp") {
let now = Date.now();
if (now - previous > wait) {
fn.apply(context, args);
previous = now;
}
} else if (type === "timeout") {
if (!timeout) {
timeout = setTimeout(() => {
timeout = null;
fn.apply(context, args)
}, wait)
}
}
}
}
区别和适用场景
通过上面的介绍,我们可以得出防抖与节流的区别。
如果事件触发频繁,防抖中的计时会不断重置,从而会不断延迟了函数的执行,而节流中的计时器不会因为事件的触发而重置。
注:以下应用并不绝对,只是写了一下我认为比较合适的解决方案便于理解防抖与节流的区别,具体应用需要根据具体场景的需求确定。
表单提交
在表单提交这一场景中,我们可以考虑使用立即执行版的防抖。
用户往往会因为提交时的等待而重复快速点击按钮进行提交表单。显然,这一过程中,表单的内容不会发生改变,我们只需要调用一次后台接口。
使用立即执行版的防抖之后,会在用户第一次点击按钮时调用一次后台接口,如果用户在延迟时间内重复点击,则不会执行表单提交函数,直至用户最后一次点击按钮并等待超过设定的延迟时间之后再次点击按钮,才会执行表单提交函数。
浏览器窗口滚动事件
之前碰到过一个滚动切换导航栏项的效果。
先获取各个元素的位置offsetTop,监听浏览器窗口滚动事件,触发该事件时,获取滚动条的位置scrollTop,遍历offsetTop与scrollTop进行比较,从而确定当前位置需要高亮哪个导航栏项。
在这一场景中,我们需要在多次事件触发的过程中,执行多次函数,因此,我们可以考虑使用时间戳版的节流(定时器版也可)。
减少函数的执行频率,防止因大量计算导致卡顿。
参考文章
https://www.jianshu.com/p/c8b86b09daf0
总结
本文主要对函数防抖与节流做了一些介绍,提供了一些实现方法,并分析了防抖与节流的区别,举例说明了适用场景。
公众号:成的学习之路。跪求一波关注= =
在公众号回复 防抖与节流, 即可获取源码地址