Debounce 和 Throttle 的原理及实现仅作为理解去抖和节流函数的概念,里面提供的代码并不一定正确
示例:
- 网上做示例,一般用scroll或者mousemove事件,我们很难控制触发事件的次数,这里我们使用click事件,我们触发了几次事件,心里都有数,更便于个人对去抖和节流函数的理解
- 以下去抖和节流函数代码均为自己理解所写,功能并不全面,但是一般的场景均能满足
- 去抖和节流函数均没有考虑返回值的情况
- 节流函数没有考虑立即执行和延时执行同时存在的情况
- 截图
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>demo</title>
<style>
* {
margin: 0;
padding: 0;
}
body,
html {
width: 100%;
height: 100%;
}
div {
float: left;
width: 250px;
height: 150px;
border: 1px solid red;
cursor: pointer;
}
</style>
</head>
<body>
<p>分别点击以下区域,并观察控制栏打印结果</p>
<div class="a">
<p>debounce方法去抖,延时执行</p>
<h3>应用:</h3>
<p>1 缩放浏览器</p>
<p>2 键盘输入文字自动提示</p>
<p>3 滚动到底部加载下一页</p>
</div>
<div class="b">
<p>debounce方法去抖,立即执行</p>
<h3>应用:</h3>
<p>1 防止用户短时间内多次提交</p>
</div>
<div class="c">
<p>throttle方法节流,延时执行</p>
<h3>应用:</h3>
<p>1 缩放浏览器</p>
</div>
<div class="d">
<p>throttle方法节流,立即执行</p>
<h3>应用:</h3>
<p>1 滚动时全屏切换,如:<a href="http://www.jq22.com/yanshi1124" target="_blank">jQuery全屏滚动插件fullPage.js演示</a></p>
</div>
</body>
<script>
let a = debounce(function (e) {
console.log('debounce方法去抖,延时执行', e.target, `事件类型:${e.type}`)
}, 500)
let b = debounce(function (e) {
console.log('debounce方法去抖,立即执行', e.target, `事件类型:${e.type}`)
}, 500, true)
let c = throttle(function (e) {
console.log('throttle方法节流,延时执行', e.target, `事件类型:${e.type}`)
}, 500)
let d = throttle(function (e) {
console.log('throttle方法节流,立即执行', e.target, `事件类型:${e.type}`)
}, 500, true)
// 绑定滚动事件
document.querySelector('.a').addEventListener('click', a)
document.querySelector('.b').addEventListener('click', b)
document.querySelector('.c').addEventListener('click', c)
document.querySelector('.d').addEventListener('click', d)
// 去抖(多次事件只执行1次,wait为延时执行时间,immediate表示首次事件是立即执行还是延时执行)
function debounce(fn, wait, immediate) {
// 闭包形成局部作用域
// 定时器
let timer = null
return function () {// #1
// 保存#1的this上下文
const self = this
// 保存#1的参数列表
const args = arguments
// 清除定时器,阻止fn的执行
clearTimeout(timer)
if (immediate) {// 事件立即执行
// 首次事件时,timer为null
let callNow = !timer
// 接下来的事件,timer都是定时器的引用,即存在
timer = setTimeout(() => {
// 延时时间过后,恢复timer为null
timer = null
}, wait)
// 首次事件时,callNow为true,所以会立即执行
if (callNow) fn.apply(self, args)
} else {// 事件延时执行
// 开启新的定时器,延时wait时间后,执行fn
timer = setTimeout(() => {
// 执行fn,改变this指向#1的上下文,并传入#1的参数
fn.apply(self, args)
}, wait)
}
}
}
// 节流(多次事件间隔执行,wait为间隔执行时间,immediate表示首次事件是立即执行还是延时执行)
function throttle(fn, wait, immediate) {
// 闭包形成局部作用域
// 定时器
let timer = null
// 开始时间
let start = 0
return function () {// #1
// 保存#1的this上下文
const self = this
// 保存#1的参数列表
const args = arguments
if (immediate) {// 立即执行
// 每次要执行fn时,记录当前时间(此时fn并未执行)
let end = new Date()
if (end - start >= wait) {// 累积时间大于间隔时间时执行(首次事件的时间戳远远大于wait)
// 执行fn,改变this指向#1的上下文,并传入#1的参数
fn.apply(self, args)
// 更新开始时间
start = end
}
} else {// 延时执行
if (!timer) {
timer = setTimeout(() => {
// 执行fn,改变this指向#1的上下文,并传入#1的参数
fn.apply(self, args)
// 赋值为null,以便下次继续执行
timer = null
}, wait)
}
}
}
}
</script>
</html>
网上更完美的封装函数
// 去抖(默认延迟执行,通过immediate来开启相应的效果)
/* // 延迟执行
debounce(function () { }, 500)
// 立即执行
debounce(function () { }, 500, true) */
function debounce(func, wait, immediate) {
var timeout, result
return function () {
var context = this
var args = arguments
if (timeout) clearTimeout(timeout)
if (immediate) {
var callNow = !timeout
timeout = setTimeout(function () {
timeout = null
}, wait)
if (callNow) result = func.apply(context, args)
}
else {
timeout = setTimeout(function () {
func.apply(context, args)
}, wait)
}
return result
}
}
// 节流(默认立即执行和延迟执行同时开启,通过options来关闭相应的效果,但是两者不能同时关闭)
/* // 立即执行和延迟执行同时开启
throttle(function () { }, 500)
throttle(function () { }, 500, {
// 取消立即执行
leading: false
})
throttle(function () { }, 500, {
// 取消延迟执行
trailing: false
}) */
function throttle(func, wait, options) {
var timeout, context, args, result
var previous = 0
if (!options) options = {}
var later = function () {
previous = options.leading === false ? 0 : new Date().getTime()
timeout = null
func.apply(context, args)
if (!timeout) context = args = null
}
var throttled = function () {
var now = new Date().getTime()
if (!previous && options.leading === false) previous = now
var remaining = wait - (now - previous)
context = this
args = arguments
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout)
timeout = null
}
previous = now
func.apply(context, args)
if (!timeout) context = args = null
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining)
}
}
return throttled
}