js 认识防抖节流 |掘金·日新计划

82 阅读7分钟

防抖节流函数介绍

浏览器的 resizescrollkeypressmousemove 等事件在触发时,会不断地调用绑定在事件上的回调函数,极大地浪费资源,降低前端性能。为了优化体验,需要对这类事件进行调用次数的限制。

防抖和节流的概念其实最早并不是出现在软件工程中,防抖是出现在电子元件中,节流出现在流体流动中

  • 而JavaScript是事件驱动的,大量的操作会触发事件,加入到事件队列中处理。
  • 而对于某些频繁的事件处理会造成性能的损耗,我们就可以通过防抖和节流来限制事件频繁的发生; 防抖和节流函数目前已经是前端实际开发中两个非常重要的函数,也是面试经常被问到的面试题

throw 方法

<script>

    function count(x, y) {

      if (!x || !y) {

        throw new Error('参数不能为空')

        //扔掉 throw 配合error使用

      }

      return x + y //返回结果1+2 =3

    }

    console.log(count(1, 2))

    console.log(count())

  </script>

接下来我们会一起来学习防抖和节流函数

  • 我们不仅仅要区分清楚防抖和节流两者的区别,也要明白在实际工作中哪些场景会用到;
  • 并且我会带着大家一点点来编写一个自己的防抖和节流的函数,不仅理解原理,也学会自己来编写;

认识防抖函数

我们用一副图来理解一下它的过程

  • 当事件触发时,相应的函数并不会立即触发,而是会等待一定的时间;
  • 当事件密集触发时,函数的触发会被频繁的推迟;
  • 只有等待了一段时间也没有事件触发,才会真正的执行响应函数;

在这里插入图片描述

防抖的应用场景很多:

  • 输入框中频繁的输入内容,搜索或者提交信息;
  • 频繁的点击按钮,触发某个事件;
  • 监听浏览器滚动事件,完成某些特定操作;
  • 用户缩放浏览器的resize事件;

防抖函数应用初步了解

<!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>

    #box {

      width: 500px;

      height: 500px;

      background: #ccc;

      font-size: 100px;

      text-align: center;

    }

  </style>

</head>

  


<body>

  <div id="box"></div>

  <script src="./lodash.js"></script>

  <script>

    const obox = document.querySelector('#box')

    let i = 1

    function mousemove() {

      obox.innerHTML = i++

    }

    // obox.addEventListener('mousemove', mousemove)

    // 利用lodash库 实现

    // obox.addEventListener('mousemove', _.debounce(mousemove, 500))

    // 1. 声明定时器变量

    // 2. 每次鼠标移动的时候要先判断是否有定时器 如果有先清除以前的定时器

    // 3. 如果没有定时器 执行定时器 存起来变量

    // 4. 定时器里写函数

    function debounce(fn,t) {

     //定时器声明

      let timer

      // 为什么是return

      //返回函数

      return function () {

       if (timer) clearTimeout(timer)

        // 先判断是否有定时器 如果有先清除以前的定时器

       timer= setTimeout(function () {

          fn()  //==mosusemove 鼠标移动

        },t) //t==时间

       

  


       }

      }

   

    obox.addEventListener('mousemove',debounce(mousemove,500)) //函数返回函数

    //函数合理化

  </script>

</body>

  


</html>

以上代码鼠标移入实现的结果

9.14 节流截图.jpg

在例如王者荣耀游戏中:

  • 王者荣耀残血回城中移动或者被控制回城会被打断
  • 技能cd中不能再释放技能 类似于上面这样的操作就是节流

防抖的简单实现

// 普通方案
window.addEventListener('resize', () => {
  console.log('trigger');
})

给窗口绑定了一个resize的事件监听器,如果触发resize就会执行后边的函数,因为resize这个动作会频繁触发,所以为了提高页面的性能,通过一个防抖函数,保证只是在用户调整好位置之后执行后边的函数。

//防抖
    function debounce(fn, delay) {
      let timer = null;
      return function () {
        let context = this,
          args = arguments
        clearTimeout(timer)  //关键:清除计时器,从头开始
        timer = setTimeout(() => { 
          fn.apply(context, args)
        }, delay)
      }
    }
    function trigger() {
      console.log('trigger')
    }
    window.addEventListener('resize', debounce(trigger, 2000))

//详细了解复制以下代码可以访问原作者
https://blog.csdn.net/weixin_38434088/article/details/115288453

防抖(debounce)

作用是在短时间内多次触发同一个函数,只执行最后一次,或者只在开始时执行。

以用户拖拽改变窗口大小,触发 resize 事件为例,在这过程中窗口的大小一直在改变,所以如果我们在 resize 事件中绑定函数,这个函数将会一直触发,而这种情况大多数情况下是无意义的,还会造成资源的大量浪费。

这时候可以使用函数防抖来优化相关操作:

作者:shawnchenxmu
链接:juejin.cn/post/684490…

function debounce(func, delay, immediate){
    var timer = null;
    return function(){
        var context = this;
        var args = arguments;
        if(timer) clearTimeout(timer);
        if(immediate){
            var doNow = !timer;
            timer = setTimeout(function(){
                timer = null;
            },delay);
            if(doNow){
                func.apply(context,args);
            }
        }else{
            timer = setTimeout(function(){
                func.apply(context,args);
            },delay);
        }
    }
}

防抖按钮点击小案例

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
    <button class="btn1">防抖请求+1</button>
    <button class="btn2" style="margin-left: 10px;">节流请求+1</button>
<script>
      const btn1 = document.querySelector('.btn1')
      const btn2 = document.querySelector('.btn2')
      let a = 0,b = 0
</script>
</body>
</html>
    // 防抖方法
      function debounce(func,delay){
        let timer = null
        return function(...args){
          // 事件再次触发就清除定时器,打断函数执行
          clearTimeout(timer)
          timer = setTimeout(()=>{
            func.apply(this,args)
          },delay)
        }
      }
  // 节流方法一
  function throttle(func,delay){
    // 设置节流阀
    let flag = true
    return function(...args){
      if(flag){
       // 关闭节流阀
        flag = false
        // 执行函数
        func.apply(this,args)
        setTimeout(()=>{
          // 定时时间结束就打开节流阀
          flag = true
        },delay)
      }
    }
  }
  // 节流方法二
   function throttle(func,delay){
   // 设置节流阀
     let timer = null
      return function(...args){
   // 只要定时器在执行就关闭节流阀,退出函数执行
        if(timer) return
        timer = setTimeout(()=>{
           func.apply(this,args)
   // 等定时时间结束就再次清空定时器,打开节流阀
            timer = null
         },delay)
      
     }
   }
      function addA(){
          a++
          console.log('防抖请求计数值为',a);
      }
      function addB(){
          b++
          console.log('节流请求计数值为',b);
      }

实现效果

(1)防抖按键:无论鼠标点的多快,只有在点击停下间隔一秒后,才能正常输出递增的数值,否则都不会执行递增和输出操作。

(2)节流按键:无论鼠标点的多快,都会以固定的频率输出递增的数值,输出间隔时间就是给节流函数设置的延时时间,这里设置的是一秒。

节流(throttle)

类似于防抖,节流是在一段时间内只允许函数执行一次。

应用场景如:输入框的联想,可以限定用户在输入时,只在每两秒钟响应一次联想。

可通过时间戳和定时器来实现。

时间戳实现:

var throttle = function(func, delay){
    var prev = Date.now();
    return function(){
        var context = this;
        var args = arguments;
        var now = Date.now();
        if(now-prev>=delay){
            func.apply(context,args);
            prev = Date.now();
        }
    }
}

区别在于,使用时间戳实现的节流函数会在第一次触发事件时立即执行,以后每过 delay 秒之后才执行一次,并且最后一次触发事件不会被执行;而定时器实现的节流函数在第一次触发时不会执行,而是在 delay 秒之后才执行,当最后一次停止触发后,还会再执行一次函数。

链接:juejin.cn/post/684490…

在这里插入图片描述

文中的const是什么

基本用法

const声明一个只读的常量。一旦声明,常量的值就不能改变。

const PI = 3.1415;
PI // 3.1415

PI = 3;
// TypeError: Assignment to constant variable.

上面代码表明改变常量的值会报错。

const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。

const foo;
// SyntaxError: Missing initializer in const declaration

上面代码表示,对于const来说,只声明不赋值,就会报错。

const的作用域与let命令相同:只在声明所在的块级作用域内有效。

if (true) {
  const MAX = 5;
}

MAX // Uncaught ReferenceError: MAX is not defined

const命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用

if (true) {
  console.log(MAX); // ReferenceError
  const MAX = 5;
}

本质

const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。

const foo = {};

// 为 foo 添加一个属性,可以成功 foo.prop = 123; foo.prop // 123

// 将 foo 指向另一个对象,就会报错 foo = {}; // TypeError: "foo" is read-only

知识总结:

节流防抖主要了解干什么

没有它会怎样