「这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战」
模拟场景
在日常开发过程中,经常会遇到频繁触发的操作,例如页面的scroll,鼠标的mousemove,键盘的keydown等等。比如在百度搜索框内输入关键字的时候,下方都会出现相关的联想词,来预测用户想要搜索的内容,我们每次敲入文字的时候都会频繁地触发。
为了更直观地体现频繁触发,我写了一段简单的代码。
<!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>
* {
margin: 0;
padding: 0;
}
body {
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
.box {
width: 300px;
height: 300px;
text-align: center;
line-height: 300px;
background-color: gray;
color: #fff;
font-size: 30px;
}
</style>
</head>
<body>
<div class="box"></div>
<script>
let box = document.querySelector('.box')
let num = 0
function addNum(){
box.innerHTML = num++
}
box.addEventListener('mousemove',addNum)
</script>
</body>
</html>
从动图中我们可以发现,随着鼠标的移动,数字会频繁地增长。当然这正是我们这段程序想要的效果,针对于数字的变化,这或许消耗不了多少的性能,但如果是频繁的网络请求,那这将会出现卡顿。
为了解决这个问题,我们可以采用防抖和节流。
今天我们就先来介绍防抖。
什么是防抖
所谓防抖,就是在触发事件n秒后再执行函数,如果在n秒内又触发了事件,则会重新计算函数执行时间。
还是以刚才的鼠标移动为例,假设我们设定的时间为1s,如果我们触发了鼠标移动事件且在1s内继续移动鼠标,那么不会执行函数,数字不会继续增加;如果我们停止触发事件,那么1s后,就会执行函数,数字会增加。
下面我们来实现防抖。
防抖的实现
非立即执行版本
function debounce(func,time){
let timer
return function(){
if(timer)
clearTimeout(timer)
timer = setTimeout(func,time)
}
}
使用方法如下:
box.addEventListener('mousemove',debounce(addNum,1000))
我们发现已经实现了想要的效果,在1s内继续触发事件则不会执行函数,而是重新计时,当我们停止触发事件的1秒后执行函数。但这样其实存在一个问题,即第一次触发事件的时候也不会执行函数。
我们需要做一些简单的修改。
立即执行版本
function debounceImmediate(func,time){
let timer
return function(){
if(timer)
clearTimeout(timer)
const callNow = !timer
timer = setTimeout(()=>{
timer = null
},time)
// 第一次触发事件的时候,还没有定时器,因此会执行一次函数
if(callNow)
func()
}
}
我们可以给防抖函数加入一个形参immediate作为判断,若为true则为立即执行版本。
最终版本
虽然我们已经实现了防抖的效果,如果直接调用debounce函数的话,会导致函数addNum中的this指向window,且没法附带原有的参数。
因此以下是防抖函数的最终版本 (immediate形参、改变this、带有参数)。
function debounceFinal(func,delay,immediate) {
let timer;
return function(){
let context = this
let args = arguments
if(timer) clearTimeout(timer)
if(immediate){
const callNow = !timer
timer = setTimeout(() => {
timer = null
}, delay);
if(callNow) func.apply(context,args)
}else{
timer = setTimeout(() => {
func.apply(context,args)
}, delay);
}
}
}
调用addNum函数
function addNum(e){
// 新增了this和e作为测试
console.log(this)
console.log(e)
box.innerHTML = num++
}
box.onmousemove = debounceFinal(addNum,1000,true)
控制台输出如下图所示。
结语
以上就是防抖函数的介绍以及实现的代码,下期我们再来介绍一下另一种方案——节流。
如有纰漏,欢迎各位指出!