本篇文章主要是介绍debounce的原理,以及如何实现自己一步一步实现debounce防抖函数。
什么是防抖
含义
事件响应函数在一段时间后执行,如果在这段时间内再次调用,则重新计算执行时间,当预定的时间内,没有再次调用该函数则执行响应函数。
使用场景
举个使用场景的🌰,input输入框变化时调用接口,这种频繁调用接口很容易造成页面卡顿,我们需要对变化时调用的函数进行防抖处理。
实现防抖
第一步:简易版
第一版实例,我们实现需要被做防抖处理的函数,在指定毫秒之后执行这个功能,下面贴出的是可以直接运行的全部代码,后面优化的时候,只贴出script里面的核心代码,实验的时候只需要进行替换即可。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#container {
height: 300px;
width: 90%;
background-color: #444;
font-size: 100px;
color: aqua;
}
</style>
</head>
<body>
<div id="container"></div>
<script>
//防抖函数
function debounce(func, wait) {//第一个参数:你要执行的函数 第二个参数:等待的时间
let timeout;
//当你调用我们这个debounce,debounce接受一个函数,然后再返回一个函数,(这就形成了一个闭包)
return function () {
//每次进来的时候清除一下timeout
clearTimeout(timeout)
timeout = setTimeout(func, wait)
}
}
let count = 0;
let container = document.querySelector('#container')
function doSomeThing() {
//可能会做回调或者ajax请求
container.innerHTML = count++
}
//高阶函数
container.onmousemove = debounce(doSomeThing, 300);
</script>
</body>
</html>
第二步:改变执行函数this指向
第二版本,我们解决了需要被防抖这个执行函数内部指向问题。这边介绍一下this指向。 《JavaScript设计模式与开发实践.pdf》一书中说过, 除去不常用的 with 和 eval 的情况,具体到实际应用中,this 的指向大致可以分为以下 4 种。
-
作为对象的方法调用
-
作为普通函数调用。
-
构造器调用
-
Function.prototype.call 或 Function.prototype.apply 调用。
当函数不作为对象的属性被调用时,也就是我们常说的普通函数方式,此时的 this 总是指向全局对象。在浏览器的 JavaScript 里,这个全局对象是 window 对象 下面doSomeThing()这个执行函数内部的this指向就是windows
当函数作为对象的方法被调用时,this 指向该对象。
debounce内部的return函数是在对象container调用debounce函数的执行的,所以this指向该对象(container)
所以我们可以利用这里的this指向改变执行函数中this指向问题。
//防抖函数
function debounce(func, wait) {
let timeout;
return function () {
console.log("debounce", this) //这里的this指向container
var that = this
clearTimeout(timeout)
timeout = setTimeout(function () {
func.apply(that) //改变执行函数container内部this的指向,原本container指向的是window
}, wait)
}
}
//执行函数doSomeThing
let count = 0;
let container = document.querySelector('#container')
function doSomeThing() {
console.log("doSomeThing", this)
container.innerHTML = count++
}
container.onmousemove = debounce(doSomeThing, 300);
第三步:解决event的指向问题
function debounce(func, wait) {
let timeout;
return function () {
var that = this
clearTimeout(timeout)
let args = arguments //解决event的指向问题
timeout = setTimeout(function (e) {
console.log('e', e)//undefined
func.apply(that, args)
}, wait)
}
}
第四步:实现防抖函数首次进入立即执行
我们可以为debounce传递第三个参数,为true表示立即执行,上面的设计是不会立即执行,鼠标进入且没有执行下一次执行函数300ms,才会执行第一次。我们想要的是进入以后立即执行。我们一开始进来的时候timeout为undefined,!timeout则为true,所以实现了一进来立即执行。立即执行完了以后callNow就开始有值了, if (callNow) { func.apply(that, args) }这个立即函数就不会再被执行了,就会执行下面的setTimeout(function () {func.apply(that, args)}, wait)。主要思想就是第一次进去立即执行
function debounce(func, wait, immediate) {
let timeout;
return function () {
var that = this
clearTimeout(timeout)
let args = arguments
//我想要鼠标进来之后立即执行
if (immediate) {
let callNow = !timeout;
timeout = setTimeout(() => {
timeout = null
}, wait)
if (callNow) {
//表示立即执行
func.apply(that, args)
}
} else {
//不会立即执行
timeout = setTimeout(function () {
func.apply(that, args)
}, wait)
}
}
}
let count = 0;
let container = document.querySelector('#container')
function doSomeThing() {
container.innerHTML = count++
}
container.onmousemove = debounce(doSomeThing, 300, true);