防抖和节流到底是什么?
防抖和节流属于性能优化的知识,它可以有效的降低高频事件触发时,你定义的方法的执行次数。
还是没有感觉???那么,来看下面的场景:
- 用户在搜索框输入关键词(只有当他输入完成时我们才去向服务器发送请求,然后给出搜索结果)
- 自动保存用户填写的表单数据
上面的场景都对应着一个高频事件,即input或者textarea的onKeyUp事件,我们一般是在用户触发这个事件后去向服务器发送请求(这样做的好处是不需要用户去点击搜索按钮,有一种实时查询的感觉)。
那么问题来了,当用户输入一个要查询的关键词,可能需要多次按下和抬起键盘的按键,难道每次onKeyUp的时候我们都要去请求服务器吗?显然不够优雅(因为如果有大量用户同时搜索,服务器压力会很大)。而 防抖(debounce) 正是要解决类似这样的问题。
在浏览器中我们经常会遇到类似的事件(如浏览器scroll,resize,mousemove...)接下来,我们使用 自动保存 的场景来说明一下在 JavaScript 中如何实现防抖。
场景描述:用户在textarea中输入文字后,要为他自动保存到服务器(可以理解为保存为草稿)这时我们需要做的是 优化 请求服务器的次数,需要用到防抖函数。
实例演示:文章中提供了在线演示的地址,便于直观的理解防抖和节流起到的作用:
防抖函数
先来看一个常见的错误写法,注意!!!百度中搜出的很多结果都是这个样子,用了之后就会发现,你的函数还是会立刻执行,并不会延时执行。
function debounce(fn, delay) {
let timer = null
return function (args) {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(fn.call(this, args), delay)
}
}
问题出在 timer = setTimeout(fn.call(this, args), delay) 这一行。
修改成下面的样子,就可以按设定的delay延时执行了:
function debounce(fn, delay) {
let timer = null
return function (args) {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(function() {
fn.call(this, args)
}, delay)
}
}
// 或者
function debounce(fn, delay) {
let timer = null
return function (args) {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(function() {
fn(args)
}, delay)
}
}
不要小看这小小的区别,它可能会浪费你大量的时间,而且让你对防抖产生怀疑...
下面贴一个完整的例子,还有 防抖在线演示地址,方便你更好的理解这个场景。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>防抖和节流</title>
<style>
.de_wrapper {
padding: 20px;
display: flex;
}
.col {
width: 40%;
}
.log {
height: 300px;
overflow-y: scroll;
background-color: #fff;
}
</style>
</head>
<body>
<div class="de_wrapper">
<div class="col">
<h3>未使用防抖(每次按键抬起都会触发保存)</h3>
<textarea name="" id="1" cols="30" rows="10" onKeyUp="printLog(event)"></textarea>
<div id="log" class="log"></div>
</div>
<div class="col">
<h3>使用防抖(停止输入2秒后保存)</h3>
<!-- <textarea name="" id="2" cols="30" rows="10"></textarea> -->
<textarea name="" id="2" cols="30" rows="10" onKeyUp="debounceLog(event)"></textarea>
<div id="log1" class="log"></div>
</div>
</div>
<script>
let log = null
let log1 = null
window.onload = function() {
log = document.getElementById('log')
log1 = document.getElementById('log1')
// 写法1
// document.getElementById('2').addEventListener('keyup', function(e) {
// debounceLog(e)
// })
// 写法2
// document.getElementById('2').addEventListener('keyup', debounceLog)
// 写法3
// document.getElementById('2').addEventListener('keyup', debounce(printDebounceLog, 2000))
}
function printLog(e) {
log.innerText += `keyup 事件触发【请求服务器保存数据...】: ${e.target.value}\n`
}
function printDebounceLog(e) {
log1.innerText += `keyup 事件触发【请求服务器保存数据...】: ${e.target.value}\n`
}
let debounceLog = debounce(printDebounceLog, 2000)
function debounce(fn, delay) {
let timer = null
return function (args) {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(function() {
fn(args)
}, delay)
}
}
</script>
</body>
</html>
节流函数
节流函数(throttle)与防抖函数的区别:函数节流无论事件触发多么频繁,在一定时间内只会执行一次回调;而函数防抖是在高频事件的最后一次触发回调。
节流函数使用场景:一个很形象的例子就是mousedown发射子弹,每秒只能发出一颗子弹,在线演示地址。
function throttle(fn, limit) {
let lastTime
return function(args) {
if (!lastTime) {
fn.apply(this,args)
lastTime = Date.now()
} else {
if ((Date.now() - lastTime) >= limit) {
fn.apply(this,args)
lastTime = Date.now()
}
}
}
}
// 页面结构
<div class="de_wrapper">
<div class="col">
<h3>未使用节流(点击按钮可以疯狂发射子弹)</h3>
<div class="sky"></div>
<button class="fire_btn">发射</button>
</div>
<div class="col">
<h3>使用节流(发射子弹速度会被限制)</h3>
<div class="sky"></div>
<button class="fire_btn">发射</button>
</div>
</div>
<script>
let sky = null
let sky1 = null
let btn = null
let btn1 = null
window.onload = function() {
sky = document.querySelectorAll('.sky')[0]
sky1 = document.querySelectorAll('.sky')[1]
btn = document.querySelectorAll('.fire_btn')[0]
btn1 = document.querySelectorAll('.fire_btn')[1]
btn.addEventListener('click', fire)
btn1.addEventListener('click', throttle(t_fire, 1000))
}
function fire() {
const b = document.createElement('span')
b.classList.add('bullet')
sky.appendChild(b)
setTimeout(() => {
sky.removeChild(b)
}, 1000)
}
function t_fire() {
const b = document.createElement('span')
b.classList.add('bullet')
sky1.appendChild(b)
setTimeout(() => {
sky1.removeChild(b)
}, 1000)
}
function throttle(fn, limit) {
let lastTime
return function(args) {
if (!lastTime) {
fn.apply(this,args)
lastTime = Date.now()
} else {
if ((Date.now() - lastTime) >= limit) {
fn.apply(this,args)
lastTime = Date.now()
}
}
}
}
总结
- 函数防抖:将多次操作合并为一次操作进行,原理是维护一个计时器,后设置的定时器会取代之前的定时器,如果高频事件一直在触发那么回调函数一直不会执行。
- 函数节流:使得一定时间内只触发一次函数。原理是通过判断是否满足限制时间,满足则执行。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。