JavaScript 防抖、节流、深拷贝、事件总线 —— (防抖函数)

125 阅读10分钟

JavaScript 防抖、节流、深拷贝、事件总线 —— 防抖函数

前几天偷懒了一会,有些知识点没有书写,但是如果想要了解的话,可以阅读Github 相关 README.md :

前端Promise总结

前端生成器和迭代器总结

前端async和await语法糖的使用

前端正则表达式和storage总结(这个写的不行)

认识防抖和节流函数

  • 防抖和节流

    • JavaScript 是一个事件驱动的语言,大量的操作会触发我们的事件,这些事件就会被添加到事件队列中进行处理
    • 对于某些频繁的事件会造成大量的性能损耗,我们这个时候就需要使用我们的防抖和节流函数来限制实现的频繁发生
  • 防抖和节流函数的话,就是两个十分重要的函数

认识防抖函数 underscore 函数实现

这个的实现的地方就是我们的在搜索框的时候使用的最多

我们实际上的话,我们在搜索框中实现书输入内容的时候,都是会向我们的服务器发送网络请求的

但是这样的每次输入都发送一次请求,服务器的压力是会很大的,所以说我们这个时候就出现了我们的防抖的这个技术,

来实现我们的尽可能的减少对服务器发送网络请求的次数,从而实现减轻服务器的压力

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
<input type="text">
<button>按钮</button>
<script>
    const inputEl = document.querySelector("input")
    inputEl.oninput = event => {
        // 应该这里进行的是网络请求的实现以及后续的操作
        console.log("正在实现我们的输入", event.target.value)
    }
​
    // 通过我们的按钮来实现我们的清空表单
    document.querySelector("button").onclick = () => {
        inputEl.value = ""
    }
</script>
</body>
</html>

我们通过上面的案例就可以知道的是,我们在搜索框中进行输入东西的时候,控制台的话是会发生每一次的请求的

我们每输入一次,就会向服务器请求一次,这样大大的增大了我们服务器的压力,所以说这个时候就需要使用防抖的技术

来从一定的程度上控制这样的结果产生

我们的防抖操作,常用的场景就是:

  • 输入框的输入内容的防抖
  • 一些回频繁点击的按钮
  • 监听浏览器的滚动事件aiin浏览器的收缩操作
  • 对于这些会频繁触发的事件都是需要进行防抖操作的,从而来实现我们的减少为页面的刷新以及对服务器的请求压力
  • 从而来实现我们的性能的优化操作

防抖函数版本一

// 开始实现手搓我们自己的防抖函数
/**
 * 防抖函数版本一
 * @param callback 回调函数
 * @param wait 回调函数执行需要等待的时间
 * @return _debounce 防抖操作后我们实现返回的新的函数
 */
function My_debounce(callback, wait) {
    let timeout = null
    const _debounce = () => {
        if (timeout) clearTimeout(timeout)
        // 防抖函数的实现一: 就是通过我们的定时器实现防抖函数的延迟触发
        timeout = setTimeout(() => {
            callback()
            // 会了实现的是不仅仅是延迟执行,同时还需要实现我们的设置最终的取消上一次执行
            timeout = null
        }, wait)
    }
    return _debounce
}

代码解读:

  • 首先我们的函数参数的实现

    • 第一个参数是我们的需要添加防抖操作的回调函数
    • 第二个就是我们实现防抖所需时间设置
  • 第二步就是使用我们的定时器实现回调函数的延迟执行

    • 但是这里只是实现了延迟执行,但是并没有实现我们真真的防抖
  • 第三步就是设置一个变量实现记录定时器

    • 这里的话,一定是定义在防抖函数外的,这里就是使用了我们的闭包的思想
    • 防止我们防抖函数的失效,根据可达性的原理,当没有一个东西实现引用指向的时候,这个时候,该对象就被销毁了
    • 同时在每次进行定时器的时候,实现我们的清理上一次的定时器,真真的达到防抖的实现
  • 版本一: 就是我们防抖函数实现时候的核心步骤

防抖函数版本二

  • 上面的防抖函数的有一个不足的一点就是,我们的真真的防抖函数是使用的是箭头函数实现的,但是总所周知的是,箭头函数是

    • 没有,this 绑定的,所以说接下来我们就要实现对防抖函数的 this 绑定问题实现解决
  • 这个问题的体现就是在我们在进行我们监听输入框的输入的时候,是需要进行的是我们的: 获取 this 的,同时我们的 this 由于

    • 我们需要添加防抖操作的函数是 callback() 默认调用的,所以说这个时候,就是 this 的四大绑定规则一的默认绑定了
    • 默认绑定的就是实现的是绑定我们的 全局对象了,浏览器环境是我们的 window,nodejs 环境就是我们的 global
  • 由于我们这里实现的是将我们内部的函数绑定给的我们的外部对象,所以说,这个时候,就可以合理的利用我们 function

    • 函数的机制,就可以轻松的实现 this 的绑定了
    • 于此同时,我们需要进行对我们的回调函数实现显示绑定执行,显示绑定 this
// 开始实现手搓我们自己的防抖函数
/**
 * 防抖函数版本一
 * @param callback 回调函数
 * @param wait 回调函数执行需要等待的时间
 * @return _debounce 防抖操作后我们实现返回的新的函数
 */
function My_debounce(callback, wait) {
    let timeout = null
    const _debounce =  function(...args)  {
        if (timeout) clearTimeout(timeout)
        // 防抖函数的实现一: 就是通过我们的定时器实现防抖函数的延迟触发
        timeout = setTimeout(() => {
            callback.apply(this, args)
            // 会了实现的是不仅仅是延迟执行,同时还需要实现我们的设置最终的取消上一次执行
            timeout = null
        }, wait)
    }
    return _debounce
}

防抖函数版本三

上面的防抖函数的书写只是成功的实现了我们的给我们的事件设置了防抖的功能

但是我们在有的时候,这个防抖操作还是需要进行取消的,所以说我们还需要向外面提供一个接口来实现我们取消防抖的功能

// 开始实现手搓我们自己的防抖函数
/**
 * 防抖函数版本一
 * @param callback 回调函数
 * @param wait 回调函数执行需要等待的时间
 * @return _debounce 防抖操作后我们实现返回的新的函数
 */
function My_debounce(callback, wait) {
    let timeout = null
    const _debounce =  function(...args)  {
        if (timeout) clearTimeout(timeout)
        // 防抖函数的实现一: 就是通过我们的定时器实现防抖函数的延迟触发
        timeout = setTimeout(() => {
            callback.apply(this, args)
            // 会了实现的是不仅仅是延迟执行,同时还需要实现我们的设置最终的取消上一次执行
            timeout = null
        }, wait)
    }
​
    // 开始实现为我们的 _debounce 添加可以取消防抖的接口
    _debounce.cancel = function() {
        if (timeout) clearTimeout(timeout)
    }
​
    return _debounce
}

防抖函数版本四

版本四的防抖函数就是实现的是我们的添加我们的立即执行函数添加

立即执行函数的添加就是为了实现的是我们的在开始防抖之前,先进行我们的触发一次,这样是为了在实现的某种功能之前的话

我们是可以直接实现让用户可以知道进行了第一次的请求的

// 开始实现手搓我们自己的防抖函数
/**
 * 防抖函数版本一
 * @param callback 回调函数
 * @param wait 回调函数执行需要等待的时间
 * @param immediate 判断我们的函数是否为立即执行的判断符
 * @return _debounce 防抖操作后我们实现返回的新的函数
 */
function My_debounce(callback, wait, immediate= false) {
    let timeout = null
    let is_executed = false
​
    const _debounce =  function(...args)  {
        if (timeout) clearTimeout(timeout)
​
        if (immediate && !is_executed) {
            callback.apply(this, args)
            is_executed = true
            return
        }
​
        // 防抖函数的实现一: 就是通过我们的定时器实现防抖函数的延迟触发
        timeout = setTimeout(() => {
            callback.apply(this, args)
            // 会了实现的是不仅仅是延迟执行,同时还需要实现我们的设置最终的取消上一次执行
            timeout = null
            is_executed = false
        }, wait)
    }
​
    // 开始实现为我们的 _debounce 添加可以取消防抖的接口
    _debounce.cancel = function() {
        if (timeout) clearTimeout(timeout)
        is_executed = false
    }
​
    return _debounce
}

防抖函数版本五

我们在实现我们的防抖函数的时候,我们有的时候还需要进行的是对我们的返回值结果进行处理

还是先来代码吧

// 开始实现手搓我们自己的防抖函数
/**
 * 防抖函数版本一
 * @param callback 回调函数
 * @param wait 回调函数执行需要等待的时间
 * @param immediate 判断我们的函数是否为立即执行的判断符
 * @return _debounce 防抖操作后我们实现返回的新的函数
 */
function My_debounce(callback, wait = 200, immediate = false) {
    let timeout = null
    let is_executed = false
​
    const _debounce =  function(...args)  {
        return new Promise((resolve, reject) => {
            try {
                if (timeout) clearTimeout(timeout)
​
                let res = undefined
                if (immediate && !is_executed) {
                    res = callback.apply(this, args)
                    is_executed = true
                    resolve(res)
                    return
                }
​
                // 防抖函数的实现一: 就是通过我们的定时器实现防抖函数的延迟触发
                timeout = setTimeout(() => {
                    res = callback.apply(this, args)
                    // 会了实现的是不仅仅是延迟执行,同时还需要实现我们的设置最终的取消上一次执行
                    timeout = null
                    is_executed = false
                    resolve(res)
                }, wait)
            } catch (err) {
                reject(err)
            }
        })
    }
​
    // 开始实现为我们的 _debounce 添加可以取消防抖的接口
    _debounce.cancel = function() {
        if (timeout) clearTimeout(timeout)
        is_executed = false
    }
​
    return _debounce
}
  • 我们这里的话就实现添加了我们的 Promise 来实现对结果的审查
  • 如果没有发生错误的话,我们就可以实现的是我们的将我们成功的结果实现返回
  • 如果发生了异常的话,我们就可以实现的是我们的返回失败的 Promise

来一个防抖函数使用的基本案例展示

// 开始实现手搓我们自己的防抖函数
/**
 * 防抖函数版本一
 * @param callback 回调函数
 * @param wait 回调函数执行需要等待的时间
 * @param immediate 判断我们的函数是否为立即执行的判断符
 * @return _debounce 防抖操作后我们实现返回的新的函数
 */
function My_debounce(callback, wait = 200, immediate= false) {
    let timeout = null
    let is_executed = false
​
    const _debounce =  function(...args)  {
        return new Promise((resolve, reject) => {
            try {
                if (timeout) clearTimeout(timeout)
​
                let res = undefined
                if (immediate && !is_executed) {
                    res = callback.apply(this, args)
                    is_executed = true
                    resolve(res)
                    return
                }
​
                // 防抖函数的实现一: 就是通过我们的定时器实现防抖函数的延迟触发
                timeout = setTimeout(() => {
                    res = callback.apply(this, args)
                    // 会了实现的是不仅仅是延迟执行,同时还需要实现我们的设置最终的取消上一次执行
                    timeout = null
                    is_executed = false
                    resolve(res)
                }, wait)
            } catch (err) {
                reject(err)
            }
        })
    }
​
    // 开始实现为我们的 _debounce 添加可以取消防抖的接口
    _debounce.cancel = function() {
        if (timeout) clearTimeout(timeout)
        is_executed = false
    }
​
    return _debounce
}
​
​
// 开始实现增加使用案例
const my_debounce = My_debounce(function() {
    console.log(this, "防抖函数实现案例")
    return "hello debounce"
})
​
// 由于我们的防抖函数返回的是 Promise
my_debounce().then(res => {
    console.log(res)
}).catch((err) => {
    console.log(err)
})
​
// 同时在 5s 后实现取消防抖
setTimeout(function () {
    my_debounce.cancel()
}, 5000)

总结

这里我们拓展一个实现自定义的接口函数的规则设计

一个变量来实现负责一个功能模块的实现

尽可能的把所有的情况考虑全面,实现后续的什么情况都了解到

我们的实现的这个防抖函数的话,最核心的功能是我们的防抖函数版本一

  • 版本一实现的是我们的防抖函数的核心代码功能
  • 版本二实现的就是我们的设计出关于防抖函数 this 绑定问题的解决
  • 版本三就是实现的是我们的增加给外部一个接口来实现取消防抖函数
  • 版本四就是实现的是我们的判断防抖函数是否执行过,然后是否开启最初执行的防抖功能
  • 版本五就是实现的是我们的对防抖函数的处理结果实现的返回