【offer 收割机之手写系列】10分钟带你掌握原理并手写防抖与节流的立即/非立即执行版本

1,257 阅读5分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

🧨 大家好,我是 Smooth,一名大二的 SCAU 前端er
🏆 本篇文章是我在复习防抖和节流时,通过手写来真正理解清楚了他们的工作原理,因此写篇文章来记录,希望能帮助到大家
🙌 如文章有误,恳请评论区指正,谢谢!
❤ 写作不易,「点赞」+「收藏」+「转发」 谢谢支持!

前言

防抖和节流是一个性能优化的手段,在面试中只要问到闭包基本必考,而且必须会手写,这篇文章就带你从原理到如何理解透彻地写出防抖节流的立即和非立即版本



函数防抖(debounce)

在事件被触发 n 秒后再执行回调(即执行你传入的函数),如果在这 n 秒内又被触发,则重新计时。

个人理解

函数防抖就是法师发技能的时候要读条,技能读条没完再按技能就会重新读条,直到技能触发(即 n 秒内没被触发)。

例子

// 模拟输入事件
<body>
  <input type="text" id='debounce' />
</body>
<script>
  // 模拟一段输入框输入事件
  function ajax(content) {
    console.log(content)
  }

  let inputb = document.getElementById('debounce');
  inputb.addEventListener('keyup', function (e) {
    ajax(e.target.value)
  })
</script>

看一下运行结果:

没防抖.gif

可以看到,我们只要按下键盘,就会触发键盘输入事件。不仅从资源上来说是很浪费的行为,而且实际应用中,用户也是输出完整的字符后,才会请求。下面我们优化一下:

加入下列函数
// 加防抖
  function debounce(fn, wait) {
    let timer;
    return function () {
      if (timer) clearTimeout(timer); // 每次执行,都先清除该计时器,然后下面重新开始计数,即不执行 debounce 一段时间 wait 后,setTimeout 里面的代码才会执行
      timer = setTimeout(() => {
        fn.apply(this, arguments);
      }, wait)
    }
  }
  
  // 包裹一下
  let debounceAjax = debounce(ajax, 500, true);
  inputb.addEventListener('keyup', function (e) {
    debounceAjax(e.target.value)
  })
演示动画

防抖非立即执行.gif

可以看到,我们加入了防抖以后,当你在频繁的输入时,并不会立即执行那个函数,只有当你在 指定间隔内 没有输入时,才会执行函数。如果停止输入但是 在指定间隔内又输入,会 重新触发 计时。

立即执行版

即第一次输入直接执行一次函数,然后再开始防抖

// 第一次立即执行,加防抖
  function debounce(fn, wait, immediate) {
    let timer;
    return function () {
      if (immediate) {
        immediate = !immediate; // 取个反
        fn.apply(this, arguments);
      }
      if (timer) clearTimeout(timer);
      timer = setTimeout(() => {
        fn.apply(this, arguments);
      }, wait)
    }
  }
演示动画

防抖立即执行.gif



函数节流(throttle)

规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。

个人理解

函数节流就是fps游戏的射速,就算一直按着鼠标射击,也只会在规定射速内射出子弹,跟你枪厉不厉害,就像是狙击枪和机枪的射速区别(射速就是节流的时间长度)。

思路:通过 时间戳 间隔是否大于 wait 了,是才执行函数,然后更新之前的时间

例子

function throttle(fn, wait) {
    let prev = new Date();
    return function () {
      const now = new Date();
      if (now - prev > wait) {
        fn.apply(this, arguments);
        prev = now;
      }
    }

}
// 其他代码类似于 防抖 例子中的,就不展示了
throttle(ajax, 2000)
演示动画

节流非立即执行.gif

可以看到,我们在不断输入时,ajax 会按照我们设定的时间,每2s执行一次。

立即执行版

即第一次输入直接执行一次函数,然后再开始节流

思路:用 计时器 实现,doNow === null 时,才进行 doNow = setTimeout 的赋值,因为赋值了嘛,所以 doNow !== null 即不会执行函数,然后到 wait 之后才重新将 doNow 赋值为 null,即允许函数执行

function throttle(fn, wait) {
    let doNow = null;
    return function () {
      if (!doNow) {
        fn.apply(this, arguments);
        doNow = setTimeout(() => {
          doNow = null;
        }, wait);
      }
    }
  }
演示动画

节流立即执行.gif

总结

  • 函数防抖和函数节流都是防止某一时间频繁触发,但是这两兄弟之间的原理却不一样。
  • 函数防抖是某一段时间内只执行一次,而函数节流是间隔时间执行。

结合应用场景

  • debounce

    • search 搜索联想,用户在不断输入值时,用防抖来节约请求资源。
    • window 触发 resize 的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次
  • throttle

    • 鼠标不断点击触发,mousedown (单位时间内只触发一次)
    • 监听滚动事件,比如是否滑到底部自动加载更多,用 throttle 来判断



最后

我是 Smoothzjc,致力于产出更多且不仅限于前端方面的优质文章

大家也可以关注我的公众号 @ Smooth前端成长记录,及时通过移动端获取到最新文章消息!

写作不易,「点赞」+「收藏」+「转发」 谢谢支持❤

往期推荐

《都2022年了还不考虑来学React Hook吗?6k字带你从入门到吃透》

《一份不可多得的 Webpack 学习指南(1万字长文带你入门 Webpack 并掌握常用的进阶配置)》

《通过 React15 ~ 17 的优化迭代来简单聊聊 Fiber》

《(建议收藏)从 URL 输入到页面展现的全过程》

《【offer 收割机之 CSS 回顾系列】请你解释一下什么是 BFC ?他的应用场景有哪些?》

《Github + hexo 实现自己的个人博客、配置主题(超详细)》

《10分钟让你彻底理解如何配置子域名来部署多个项目》

《一文理解配置伪静态解决 部署项目刷新页面404问题

《带你3分钟掌握常见的水平垂直居中面试题》

《【建议收藏】长达万字的git常用指令总结!!!适合小白及在工作中想要对git基本指令有所了解的人群》

《浅谈javascript的原型和原型链(新手懵懂想学会原型链?看这篇文章就足够啦!!!)》