函数防抖

306 阅读4分钟

本篇文章主要是介绍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);