防抖是控制次数,节流是控制频率
防抖(debounce)
原理:在事件被触发 n 秒后再执行回调,如果在这 n 秒内又被触发,则重新计时
效果:如果短时间内大量触发同一事件,只会执行一次函数
适用场景:
- 防止多次提交按钮,只执行最后提交的一次
- 防止搜索框联想频繁发送请求
- 防止频繁监听浏览器滚动事件
- ……
示例:
假设鼠标滑过一个 div,触发 onmousemove 事件,它内部的文字会显示当前鼠标的坐标
在上边的场景下,我们不希望触发一次就执行一次,这就要用到防抖或节流
封装一个函数,让持续触发的事件监听是我们封装的这个函数,将目标函数作为回调(func)传进去,等待一段时间过后执行目标函数
function debounce(func, wait) {
let timeout // 由于返回的函数是一个闭包,所以 timeout 并不会回收
return function() {
let context = this;
let args = arguments;
if (timeout) clearTimeout(timeout) // 如果持续触发,那么就清除定时器,定时器的回调就不会执行
timeout = setTimeout(() => {
func.apply(context, args)
}, wait)
}
}
上面的示例是非立刻执行的,也就是最后才执行一次,而立即执行的是一旦触发事件后会立即执行一次
function debounce(func, wait) {
let timeout
return function() {
let context = this;
let args = arguments;
if (timeout) clearTimeout(timeout) // 清除之后还是为 true
let callNow = !timeout;
timeout = setTimeout(() => {
timeout = null;
}, wait)
if (callNow) func.apply(context, args)
}
}
可以将非立即执行版和立即执行版的防抖函数结合起来
/**
* @desc 函数防抖
* @param func 函数
* @param wait 延迟执行毫秒数
* @param immediate true 表立即执行,false 表非立即执行
*/
function debounce(func,wait,immediate) {
let timeout;
return function () {
let context = this;
let args = arguments;
if (timeout) clearTimeout(timeout);
if (immediate) {
var callNow = !timeout;
timeout = setTimeout(() => {
timeout = null;
}, wait)
if (callNow) func.apply(context, args)
}
else {
timeout = setTimeout(function(){
func.apply(context, args)
}, wait);
}
}
}
节流(throttle)
原理:设置一个开关,持续触发时关闭开关,n 秒后执行函数并打开开关
效果:如果单位时间内触发多次函数,只有一次生效
适用场景:
- 拖拽场景防止超高频次触发位置变动
- 缩放场景防止频繁监控浏览器 resize
- 防止搜索框联想频繁发送请求,在指定单位时间内又必须触发
- ……
示例:
同样是触发 onmousemove 事件
function throttle (func, delay) {
let timeout
return function () {
let context = this
let args = arguments
if (!timeout) {
timeout = setTimeout(() => {
timeout = null
func.apply(context, args)
}, delay)
}
}
}
上面的示例是利用定时器,也可以利用时间戳
function throttle (func, wait) {
let previous = 0
return function () {
let now = Date.now()
let context = this
let args = arguments
if (now - previous > wait) {
func.apply(context, args)
previous = now
}
}
}
两者相结合:
/**
* @desc 函数节流
* @param func 函数
* @param wait 延迟执行毫秒数
* @param type 1 表时间戳版,2 表定时器版
*/
function throttle (func, wait, type) {
if (type === 1) {
let previous = 0
} else if (type === 2) {
let timeout
}
return function () {
let context = this
let args = arguments
if (type === 1) {
let now = Date.now()
if (now - previous > wait) {
func.apply(context, args)
previous = now
}
} else if (type === 2) {
if (!timeout) {
timeout = setTimeout(() => {
timeout = null
func.apply(context, args)
}, wait)
}
}
}
}