一、引入

为什么需要防抖和节流

我们现在JS主要是在浏览器下运行,涉及到很多人机交互的操作。假设一个场景,我们打开的网页有一个轮播图,我们对左右切换的按钮疯狂点击。按钮是绑定了操作的,那么每次我们点击这个按钮被监听到后就会执行代码,有部分内存会被使用,性能也有所消耗。这就是所谓的高频次触发的场景。在这种场景下,我们希望事件对应的监听不需要立即或者说反复被触发,这样我们就需要对其进行防抖和节流的操作。

使用场景

  • 滚动事件
  • 输入的模糊匹配
  • 轮播图切换
  • 点击操作
  • ……

浏览器默认情况下都会有自己的监听事件间隔,比如Chrome浏览器监听间隔是4-6ms。如果检测到多次事件的监听执行,那么就会造成不必要的资源浪费。

概念

防抖:对于高频操作来说,我们只希望只识别一次,可以认为认为是第一次或者最后一次。

节流:对于高频操作,我们可以自己设置频率,让本来会执行很多次的事件触发,按照我们定义的频率减少触发次数。

二、防抖函数实现

场景

有一个按钮,我们对其进行高频触发,但是希望只识别一次。

原始代码

这个代码下,就是点击一次执行一次。


 

<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>防抖函数实现</title> <body> <button id="btn" >点击</button> <script> //找到目标元素 var oBtn = document.getElementById('btn') oBtn.onclick = function () { console.log('点击了') } </script> </body> </html>

防抖函数代码

里面具体的思路和每一块是干什么的,都写在代码注释里面了,这边就不展开了。


 
  1.  
    <!DOCTYPE html>
  2.  
    <html>
  3.  
    <head>
  4.  
    <meta charset="UTF-8">
  5.  
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6.  
    <title>防抖函数实现</title>
  7.  
    </head>
  8.  
    <body>
  9.  
    <button id="btn" >点击</button>
  10.  
    <script>
  11.  
    //找到目标元素
  12.  
    var oBtn = document.getElementById('btn')
  13.  
    // oBtn.onclick = function () {
  14.  
    // console.log('点击了')
  15.  
    // }
  16.  
    /**
  17.  
    * handle 要执行的操作
  18.  
    * wait 事件触发后多久开始执行
  19.  
    * immediate 控制执行第一次还是最后一次,如果是false就执行最后一次,如果是true就执行第一次
  20.  
    */
  21.  
    // 其实这些参数我们可以考虑给他们添加默认值比如wait = 300, immediate = false。
  22.  
    // 但是定义一个函数给它的形参赋予一个默认值,这样的话它整个函数体里的代码将来如果出现一些变量定义之后,会有一些比较变态的机制.这边不做讨论,所以先不设置默认值了
  23.  
    function myDebounce (handle,wait,immediate) {
  24.  
    //参数类型判断及默认值处理
  25.  
    if(typeof handle !== 'function') throw new Error('handle must be a function')
  26.  
    if(typeof wait === 'undefined') wait = 300
  27.  
    if(typeof wait === 'boolean') {
  28.  
    immediate = wait
  29.  
    wait = 300
  30.  
    }
  31.  
    if(typeof immediate === 'undefined') immediate = false
  32.  
     
  33.  
    // 所谓的防抖效果,我们想要实现的是有一个”人“,可以管理handle的执行次数
  34.  
    // 如果我们想要执行最后一次,那意味着无论我们当前点击了多少次,前面的N-1次都没有用
  35.  
    let timer = null
  36.  
    return function proxy(...args) {
  37.  
    let self = this
  38.  
    init = immediate && !timer
  39.  
    clearTimeout(timer)
  40.  
    timer = setTimeout(() => {
  41.  
    timer = null
  42.  
    //只有我们的immediate为false才执行下面这个代码,否则就不执行【原本其实就是handle(),但是为了拿到this和mouseEvent所以改成下面这个样子】
  43.  
    !immediate ? handle.call(self, ...args) : null
  44.  
    },wait)
  45.  
    //如果当前传进来的是true,就表示我们需要立即执行
  46.  
    // 如果想要实现只在第一次执行,可以添加上timer为null作为判断,因为只要timer为null,就意味着没有第二次
  47.  
    init ? handle.call(self, ...args) : null
  48.  
    }
  49.  
    }
  50.  
    // 定义事件执行函数,这里的ev是点击的MouseEvent,this是button元素
  51.  
    function btnClick(ev) {
  52.  
    console.log('点击了', this, ev)
  53.  
    }
  54.  
    // 当我们执行了按钮点击之后就会执行防抖函数返回的proxy
  55.  
    //oBtn.onclick = myDebounce(btnClick,false)
  56.  
    oBtn.onclick = myDebounce(btnClick,200,true)
  57.  
    </script>
  58.  
    </body>
  59.  
    </html>
 

三、节流函数实现

场景

一个页面我们往下滚动,触发相应事件。

原始代码

这个代码下,我们只要做出滚动的动作就会被监听到,不断触发scrollFn方法。但是这其实是会造成资源浪费的,我们并不需要这么高频次的响应触发。


 
  1.  
    <!DOCTYPE html>
  2.  
    <html>
  3.  
    <head>
  4.  
    <meta charset="UTF-8">
  5.  
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6.  
    <title>节流函数实现</title>
  7.  
    <style>
  8.  
    body {
  9.  
    height:5000px
  10.  
    }
  11.  
    </style>
  12.  
    </head>
  13.  
    <body>
  14.  
    <script>
  15.  
    // 定义滚动事件监听
  16.  
    function scrollFn() {
  17.  
    console.log('滚动了')
  18.  
    }
  19.  
    window.onscroll = scrollFn
  20.  
    </script>
  21.  
    </body>
  22.  
    </html>
 

节流函数代码

 我们这里的节流指的是在自定义的一段事件内让事件进行触发


 
  1.  
    <!DOCTYPE html>
  2.  
    <html>
  3.  
    <head>
  4.  
    <meta charset="UTF-8">
  5.  
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6.  
    <title>节流函数实现</title>
  7.  
    <style>
  8.  
    body {
  9.  
    height:5000px
  10.  
    }
  11.  
    </style>
  12.  
    </head>
  13.  
    <body>
  14.  
    <script>
  15.  
    // 节流: 我们这里的节流指的是在自定义的一段事件内让事件进行触发
  16.  
    function myThrottle(handle,wait) {
  17.  
    if(typeof handle !== 'function') throw new Error('handle must be a function')
  18.  
    if(typeof wait === 'undefined') wait = 400
  19.  
     
  20.  
    let previous = 0 // 定义变量记录上一次执行的时间
  21.  
    let timer = null // 用来管理定时器
  22.  
    return function proxy(...args) {
  23.  
    let self = this
  24.  
    let now = new Date()
  25.  
    let interval = wait - ( now - previous)
  26.  
    if(interval <= 0){
  27.  
    //万一很巧的定时器延迟到的操作和我们的点击同时进行
  28.  
    clearTimeout(timer)
  29.  
    timer = null
  30.  
    //非高频次操作,可以执行handle
  31.  
    handle.call(self, ...args)
  32.  
    previous = new Date()
  33.  
    }
  34.  
    //当我们发现系统中有一个定时器了,就不需要再开启定时器了
  35.  
    else if(!timer){
  36.  
    //这次操作发生在我们定义的频次时间范围内,不应该执行
  37.  
    // 这个时候可以自己定义一个定时器,让handle在interval之后去执行
  38.  
    timer = setTimeout(() => {
  39.  
    clearTimeout(timer)// 这个操作知识将系统中的定时器清楚了,但是timer中的值还在,所以需要手动吧timer = null
  40.  
    timer = null
  41.  
    handle.call(self, ...args)
  42.  
    previous = new Date()
  43.  
    },interval)
  44.  
    }
  45.  
    }
  46.  
    }
  47.  
    // 定义滚动事件监听
  48.  
    function scrollFn() {
  49.  
    console.log('滚动了')
  50.  
    }
  51.  
    //window.onscroll = scrollFn
  52.  
    window.onscroll = myThrottle(scrollFn,600)
  53.  
    </script>
  54.  
    </body>
  55.  
    </html>