一文彻底搞懂:js函数防抖与节流

998 阅读4分钟

前言

本文介绍了js中常见的2中函数优化机制,函数防抖与节流,他们都是用来防止函数被频繁调用的,但使用的场景略有差别。

防抖

我们在访问百度、谷歌、淘宝等网站的时候经常遇到边输入边提示的情况,网站会根据我们输入的关键词推测我们想要搜索的内容,提升用户体验。如下所示:

提示信息

提示信息

我们来考虑一下如何实现这个功能。首先我们需要监听输入框的内容变化,当内容变化的时候需要向服务端发送网络请求获取所示提示内容,如下代码所示:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>函数防抖</title>
</head>
<body>
    <input id='input'/>
    <script>
        const input = document.getElementById('input')
        let timerId = null
        input.oninput = function (event) {
                    console.log(event.srcElement.value)
        }
    </script>
</body>
</html>

通过oninput 函数我们可以拿到用户的输入值,然后去发送网络请求。这里有一个坑需要我们注意,因为每次用户输入都会触发oninput方法,假设平均一次网络请求所需要的时间是1s,假如1s内用户输入3个字符ABC,如果输入框的值一变化我们就发送网络请求,那么就会发送三个网络请求,关键字分别为AABABC 但,服务端网络平均耗时为1s,也就是说 你输入ABC的时候,第一个和网络请求才返回,但这时用户已经输入到ABC 了,这时候再显示A 或者B 的推荐结果显示不合适。

怎么办?我们应该避免频繁的请求,一方面是为了减轻服务端的压力,一方面是为了得到更好的用户前端体验,我们需要限制网络请求频率,这可以通过定时器来完成,这就是函数防抖:通过定时器来减少不必要的函数的调用频率,以便得到更好的性能和用户体验。

let timerId = null
input.oninput = function (event) {
  if (timerId) {
    clearTimeout(timerId)
  }
  timerId = setTimeout(() => {
    console.log(event.srcElement.value)
  }, 1000);
}

上面代码中,我们首先声明了一个定时器变量,当触发输入框的值变化的时候,我们先判断定时器是否有值,如果有值就取消它,并重新给他赋值。它表达的意思是:当用户停止输入1s后再去处理网络请求,帮助用户提示。

我们可以将它封装成固定的防抖函数debounce, 它接收两个参数,第一个参数fn:表示需要执行的任务,第二个参数delay: 表示定时器的延迟时间。

function debounce(fn, delay) {
  let timerId = null
  let self = this
  return function() {
    if (timerId) {
      clearTimeout(timerId)
    }
    let args = arguments
    timerId = setTimeout(() => {
      fn.apply(self, args)
    }, time);
  }
}

其中值得注意的是:1. 需要fn需要使用apply进行this的绑定,保证其与debounce 执行的环境一样。2. 需要使用arguments 将外传出事件参数。

之后我们可以修改oninput事件,如下:

function test(event) {
  console.log(event)
}
input.oninput = debounce(test, 1000)

节流

节流与防抖类似,防抖的特点是:指定的行为停止指定的时间之后,再执行对应的任务。但是有些场景下我们需要在中间状态执行对应的任务,比如如下场景:我们在浏览器的可视区域中心放一个长方形,当改变浏览器的可视区域大小后,长方形的大小也跟随改变,始终保持长方形的大小为可视区域的1/4。如果我们用下面的防抖函数实现,长方形只会在停止拖动的时候改变大小,显的比较突兀,我们希望在拖动的过程中也能看到长方形大小的变化。

const input = document.getElementById('root')

function debounce(fn, time) {
  let timerId = null
  let self = this
  return function() {
    if (timerId) {
      clearTimeout(timerId)
    }
    let args = arguments 
    timerId = setTimeout(() => {
      fn.apply(self, args)
    }, time);
  }
}


function reSetSize() {
  let width = window.innerWidth
  let height = window.innerHeight
  input.style.width = width/2 + 'px'
  input.style.height = height/2 + 'px'
}
reSetSize()
window.onresize = debounce(reSetSize, 1000);

我们可以对防抖函数进行改造,让它变成节流函数,通过增加一个flag标识,这个函数就变成了每n时长执行对应的操作。

function throttle(fn, time) {
  let flag = true
  let timerId = null
  let self = this
  return function() {
    if (!flag) {
      return
    }
    flag = false
    if (timerId) {
      clearTimeout(timerId)
    }
    let args = arguments 
    timerId = setTimeout(() => {
      flag = true
      fn.apply(self, args)
    }, time);
  }
}

改变onresize 的监听,就可以看到边拖动边改变长方形的效果了。

window.onresize = debounce(reSetSize, 1000);

总结

通过本文,你了解到了函数的防抖与节流,他们的区别是:

  • 防抖 当某一行为停止之后过n时长再执行响应的操作。
  • 节流 每个n时长就执行一次函数。

推荐阅读

点赞,关注 是你对我最大的鼓励。

更多优质内容,请扫码关注公众号:RiverLi