JS深入理解—模拟实现防抖函数

前言

在理解防抖之前,先来了解下出现抖动的场景:

  1. 开发搜索功能的时候,输入字符执行查询操作,network 中会瞬间出现无数的ajax请求
  2. 进行页面适配,调整窗口大小的时候适配不同的布局,如果适配业务复杂,浏览器可能出现卡顿现象

抖动:频繁触发而导致不可预测后果的现象。

防抖:防止在短时间内频繁触发,一段时间内只触发一次。

我们常常遇到的一些频繁触发的事件有:

  1. 输入框的 keyup / keydown
  2. 调整窗口大小的 resize
  3. 页面滚动的 scroll
  4. 鼠标滑动的 mousedown / mousemove

抖动现象

接下来以实现输入关键字搜索功能为例:

index.html代码如下:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>debounce</title>
  </head>
  <body>
    <div>
      <input id="search" type="text" />
      <div id="result"></div>
      <script>
        let count = 0;
        let result = document.getElementById("result");
        let search = document.getElementById("search");
        search.addEventListener("keyup", onSearch);

        function onSearch() {
          console.log(this);
          result.innerHTML = count++;
          // TODO: 发送ajax请求
        }
      </script>
    </div>
  </body>
</html>

运行这个示例,我们可以看到,每次敲一个字符都会触发事件,对于简单的逻辑没有问题,但如果每次触发都发送ajax请求,那么这个页面就凉凉了,直接导致页面卡顿。为了解决这个问题,我们来写个防抖函数。

实现防抖

防抖的原理:尽管触发事件,让业务代码控制在 n 秒后才执行。

v0.1-基础功能

function debounce(func, wait = 1000) {
    let timeout;
    return function() {
        clearTimeout(timeout);
        timeout = setTimeout(func,wait);
    }
}

那么,调用的代码就改成如下:

let count = 0;
let result = document.getElementById("result");
let search = document.getElementById("search");
search.addEventListener("keyup", debounce(onSearch, 1000));
function onSearch() {
    console.log(this);
    result.innerHTML = count++;
}

此时,你会发现,在1s内,只会触发一次业务代码,暂时达到了效果。

v0.2-指向this和e

如果我们在onSearch函数中使用console.log(this)console.log(e)

在不使用 debounce 的情况下输出如下:

// this
<input id="search" type="text" />
// event
KeyboardEvent {isTrusted: true, key: "2", code: "Digit2", location: 0, ctrlKey: false,}

加上 debounce 后的输出情况如下:

// this
Window {parent: Window, postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ,}
// event
undefined

显然后者的指向是有问题的,我们接下来做第二次改动:

function debounce(func, wait = 1000) {
    let timeout;
    return function() {
        let that = this; // 保存this指向
        let args = arguments; // 保存arguments
        clearTimeout(timeout);
        timeout = setTimeout(function() {
            func.apply(that, args); // 传递给func内部
        }, wait);
    };
}

此时,this 和 e 的指向就正确了!

扩展个知识点:我们也可以用call/bind来代替apply。

func.apply(that, args); // 所有参数都必须放在一个数组里面传进去
func.call(that, args);  // 参数是直接放进去的,通过逗号分割
func.bind(that, args)();// 和 call的效果一样

v0.3-立刻执行

如果希望立刻执行函数,然后等到停止触发n秒后,才可以重新触发执行,该如何执行? 实际的业务开发中,笔者很少见到这种场景。我们可以增加immediate来控制。

function debounce(func, wait = 1000, immediate = false) {
    let timeout;
    return function() {
        let that = this;
        let args = arguments;
        if (timeout) clearTimeout(timeout);
        // 立刻执行
        if (immediate) {
            let rightNow = !timeout; // 通过该参数控制只执行一次
            timeout = setTimeout(function() {
                timeout = null;
            }, wait);
            if (rightNow) {
                func.apply(that, args);
            }
        } else {
            timeout = setTimeout(function() {
                func.apply(that, args);
            }, wait);
        }
    };
}

v0.4-取消执行

试想一下,如果设置了immediate为true,只有等n秒后才能能重新触发,现在希望能够取消防抖,让它能够立刻恢复执行。

function debounce(func, wait = 1000, immediate = false) {
    let timeout;
    let debounce = function() {
	    let that = this;
	    let args = arguments;
	    if (timeout) clearTimeout(timeout);
	    // 立刻执行
	    if (immediate) {
	        let rightNow = !timeout; // 通过该参数控制只执行一次
	        timeout = setTimeout(function() {
	            timeout = null;
	        }, wait);
	        if (rightNow) {
	            func.apply(that, args);
	        }
	    } else {
	        timeout = setTimeout(function() {
	            func.apply(that, args);
	        }, wait);
	    }
    };
    // 取消
    debounce.cancel = function() {
        clearTimeout(timeout);
        timeout = null;
    }
    return debounce;
}

接下来,修改下调用的地方:

let count = 0;
let result = document.getElementById("result");
let search = document.getElementById("search");
let cancel = document.getElementById("cancel");
let doSearch = debounce(onSearch, 10000, true)
search.addEventListener("keyup", doSearch); // 执行搜索
cancel.addEventListener("click", doSearch.cancel); // 取消搜索
function onSearch(e) {
    console.log(this);
    console.log(e);
    result.innerHTML = count++;
}

到此为止,我们已经逐步衍化出一个可以使用的防抖函数了,但是和lodash中的_.debounce还是有一定差距的,有兴趣深入的同学可以参考。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值