手写防抖、节流

345 阅读5分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

前言

相信大家多多少少在学习过程中了解过或者听说过防抖节流,其实防抖和节流不仅仅会在面试时面试官让你手撕(手写代码),在实际的项目开发中也可以起到优化性能的作用,所以话不多少,让我们开始今天的学习。

一、防抖

当事件触发时(接口请求、点击事件等),相应的函数并不会立即触发执行,而是会推迟执行,当事件连续密集被触发时,相应的函数会一直被推迟,若在推迟的时间内再次触发事件,那么延迟的时间会重置,只有在最后一次时间触发的延迟时间到达后,才会执行函数,并且只执行最后一次的事件函数。

定义:N秒后事件触发,若在N秒内重复触发,则重新计时N秒,只执行最后一次的相应函数。

举个简单的例子,当我们在乘坐电梯时,假设每次有人进入电梯之后十五秒开始运行,把电梯完成一次运输可类比为一次函数的执行和响应。当第一个人进入电梯后,电梯等待15s,若15秒内有人再次进入电梯重新计时15s直到没人进入电梯的15s后,电梯运行,这就是防抖。

image.png

代码实现(防抖)

假设有一位得了帕金森的老人正在购票,点击购买时手抖连续点了很多下,当然铁路局不会让每次点击都调用新的接口,所以如何实现防抖功能呢?

  • 首先我们模拟一个按钮被点击(点击事件)
HTML部分:

<button id="btn">提交</button>

JS部分:

let btn = document.getElementById('btn');
function ajax(){
   console.log('发送请求');
}
   
btn.addEventListener('click',ajax );
  • 我们使用闭包封装一下仿接口函数ajax,同时加上一个定时器
let btn = document.getElementById('btn');

    function ajax(){
      console.log('发送请求');
    }

 var lazyLayout = debounce(ajax, 500);
    
 function debounce(fn, wait) {

    return function () {
    setTimeout(() =>{
            fn()
        }, wait)
    }
}
    
    btn.addEventListener('click',lazyLayout )
  • 现在只实现了当事件连续密集被触发时,相应的函数会一直被推迟,但是有多少次点击我们就会执行多少次,如果我们要实现只执行一次,那么我们只要每次新增的setTimeout将上一次的定时器用clearTimeout清除上一次的定时器,所以最后只会有一次执行。
let btn = document.getElementById('btn');

    function ajax(){
      console.log('发送请求');
    }

 var lazyLayout = debounce(ajax, 500);
    
 function debounce(fn, wait) {
    var timeout;
    return function () {
    clearTimeout(timeout) // 清除上一次的定时器
    timeout = setTimeout(() =>{
            fn()
        }, wait)
    }
}
    
btn.addEventListener('click',lazyLayout )
  • 这样一个防抖函数的基本功能就实现了,但是这个时候ajax的this指向以及事件参数也会受到改变,因此我们需要将ajax函数的this指向以及事件参数显示绑定到防抖函数中,这样我们的完成的防抖功能就实现了,并且不会影响this指向和事件参数,处理参数我们可以使用arguments处理。
let btn = document.getElementById('btn');

    function ajax(){
      console.log('发送请求');
    }

 var lazyLayout = debounce(ajax, 500)
 
 function debounce(fn, wait) {
    var timeout;
    return function () {
        const context = this;
        var args = arguments;// 类数组
        clearTimeout(timeout);
        timeout = setTimeout(() =>{
            fn.apply(context,args)
        }, wait)
    }
}

btn.addEventListener('click',lazyLayout )

这样一个简单的防抖函数我们就是手写完成了,换言之防抖就是在不断的事件触发时最终只执行一次的一种优化性能的方法。

二、节流

定义:n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效。

同样是乘电梯的模型,当第一个人进来之后15s后准时运行一次,这就是节流,当然这个模型可能不太生动,那我们换个模型,就是公园的自动洒水机,每隔30min自动运行一次,当距离上次自动洒水的时间间隔未达到30min时,始终保持静止状态,当再次达到30min时,又会自动执行一次洒水,这就是节流。

image.png

而节流函数的实现其实与防抖类似,我们也使用仿接口函数ajax和闭包等操作来实现,节流函数的this指向以及事件参数,通过apply(显示绑定),这样一个简易版的节流函数就实现了。

代码实现(节流)

// 节流函数与防抖函数一样,是借助了计时器和闭包来实现的,初始状态标记为null,设置一个定时器
// 在定时器执行完时将标记重新设置为null,当标记!= null再执行函数
function throttle(fn,wait) {
  let flag = null;
  return function() {
    const context = this;
    let args = arguments;
    if(!flag) {
      flag = setTimeout(() => {
        flag = null;
        fn.apply(context,args);
      },wait)
    }
  }
}

节流函数的实现,我们通过一个初始时间,与当前时间的差的时间戳与我们设置的执行时间相对比,若大于则执行一次,否则不执行,每次执行之后,将当前时间赋值给初始时间,再进行下一次判断,达到节流的效果。换言之节流就是在一段时间内不断操作而在你规定的时间内只执行一次的一种提高性能的方法。

三、防抖和节流的异同

相同点

  • 目的都是,降低回调执行频率。节省计算资源;

不同点

  • 函数防抖,在一段连续操作结束后,处理回调,利用clearTimeout和 setTimeout实现。函数节流,在一段连续操作中,每一段时间只执行一次,频率较高的事件中使用来提高性能;
  • 函数防抖关注一定时间连续触发的事件,只在最后执行一次,而函数节流一段时间内只执行一次;
image.png

四、应用场景

防抖在连续的事件,只需触发一次回调的场景有:

  • 搜索框搜索输入。只需用户最后一次输入完,再发送请求
  • 手机号、邮箱验证输入检测
  • 窗口大小resize。只需窗口调整完成后,计算窗口大小。防止重复渲染。

节流在间隔一段时间执行一次回调的场景有:

  • 滚动加载,加载更多或滚到底部监听
  • 搜索框,搜索联想功能