前言

关于防抖和节流

在进行窗口的 resize、scroll、输出框内容校验等操纵的时候,如果事件处理函数调用的频率无限制,会加重浏览器的负担,导致用户体验非常之差。那么为了前端性能的优化也为了用户更好的体验,就可以采用防抖(debounce)和节流(throttle)的方式来到达这种效果,减少调用的频率。

为什么滚动 scroll、窗口 resize 等事件需要优化

Web 页面展示经历的步骤:js—style—layout—paint—composite

滚动事件的应用很频繁:图片懒加载、下滑自动加载数据、侧边浮动导航栏等。

网页生成的时候,至少会渲染(Layout+Paint)一次。用户访问的过程中,还会不断重新的重排(reflow)和重绘(repaint),用户 scroll 行为和 resize 行为会导致页面不断的进行重新渲染,而且间隔频繁,事件中涉及到的大量的位置计算、DOM 操作、元素重绘等等这些都无法在下一个 scroll 事件出发前完成的情况下,容易造成浏览器卡帧。


防抖 Debounce

防抖情景

  1. 有些场景事件触发的频率过高(mousemove onkeydown onkeyup onscroll)如滚动监听,商品秒杀等
  2. 回调函数执行的频率过高也会有卡顿现象,可以一段时间过后进行触发,去除无用操作
1
2
3
4
5
function showTop() {
var scrollTop = document.documentElement.scrollTop;
console.log("滚动条位置:" + scrollTop);
}
window.onscroll = showTop;

防抖原理

一定在事件触发 n 秒后才执行,如果在一个事件触发的 n 秒内又触发了这个事件,以新的事件的时间为准,n 秒后才执行,等触发事件 n 秒内不再触发事件才执行。

官方解释:当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时。

“你别一直抖!你只要抖了我就不干活了,什么时候不抖了我再接着干。”

防抖函数简单实现

  • 使用了延时器和闭包,有效解决了执行频率过高的问题,让某个时间期限内只执行一次事件处理函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // 简单防抖函数
    function debounce(fn, wait) {
    var timer = null;
    // 闭包
    return function () {
    if (timer) {
    clearTimeout(timer);
    }
    timer = setTimeout(fn, wait);
    };
    }

    function showTop() {
    var scrollTop = document.documentElement.scrollTop;
    console.log("滚动条位置:" + scrollTop);
    }

    window.onscroll = debounce(showTop, 300);

节流 Throttle

节流情景

  1. 图片懒加载
  2. ajax 数据请求加载

节流原理

如果持续触发事件,每隔一段时间只执行一次函数。

官方解释:当持续触发事件时,保证一定时间段内只调用一次事件处理函数。

“你别一直催!你催得再急我也是干这么快,这点时间我只完成一次。”

节流函数简单实现

  • 使用了延时器和闭包,即便一直滚动不松手,也会在指定时间间隔时执行一次函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // 简单节流函数
    function throttle(fn, wait) {
    var valid = true;
    // 闭包
    return function () {
    if (!valid) {
    return false;
    }
    valid = false;
    setTimeout(function () {
    fn();
    valid = true;
    }, wait);
    };
    }

    function showTop() {
    var scrollTop = document.documentElement.scrollTop;
    console.log("滚动条位置:" + scrollTop);
    }

    window.onscroll = throttle(showTop, 1000);

实际开发场景

总的来说,在某事件持续触发时,防抖只会等最后一次触发后执行某函数,节流会保证在指定间隔时间段内执行某函数。

  1. 搜索框input事件,例如要支持输入实时搜索:可以使用节流方案(间隔一段时间就必须查询相关内容);或者使用防抖方案,实现输入间隔大于某个值(如500ms),就当做用户输入完成,然后开始搜索。(具体使用哪种方案要看业务需求)
  2. 页面resize事件,常见于需要做页面适配的时候,需要根据最终呈现的页面情况进行dom渲染(这种情形一般是使用防抖,因为只需要判断最后一次的变化情况)

总结与代码示例

本部分内容由微软 New Bing 撰写

防抖和节流都是前端性能优化的方法,用于控制函数的执行频率。

  1. 防抖是指在一定时间内,只有最后一次操作才会触发函数执行,例如搜索框输入时的自动提示2。节流是指在一定时间内,只允许函数执行一次,例如滚动条滚动时的图片懒加载。
  2. 防抖和节流的目的是为了减少不必要的计算和渲染,提高页面性能和用户体验。
  • 防抖的代码示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    // 防抖函数
    function debounce(fn, delay) {
    // 定义一个定时器变量
    let timer = null;
    // 返回一个新的函数
    return function() {
    // 清除上一次的定时器
    clearTimeout(timer);
    // 获取函数的执行上下文和参数
    let context = this;
    let args = arguments;
    // 设置一个新的定时器,延迟执行函数
    timer = setTimeout(function() {
    fn.apply(context, args);
    }, delay);
    }
    }

    // 测试用例:输入框搜索功能
    let input = document.getElementById('input');
    let ajax = function(value) {
    console.log('发送请求,搜索关键词:' + value);
    }
    // 给输入框绑定防抖后的事件处理函数
    input.addEventListener('input', debounce(ajax, 500));
  • 节流的代码示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // 节流函数
    function throttle(fn, delay) {
    // 定义一个标志位,表示是否可以执行函数
    let flag = true;
    // 返回一个新的函数
    return function() {
    // 如果标志位为false,表示不能执行,直接返回
    if (!flag) return;
    // 如果标志位为true,表示可以执行,先将标志位设为false
    flag = false;
    // 获取函数的执行上下文和参数
    let context = this;
    let args = arguments;
    // 设置一个定时器,延迟恢复标志位为true,并执行函数
    setTimeout(function() {
    flag = true;
    fn.apply(context, args);
    }, delay);
    }
    }

    // 测试用例:滚动条滚动事件监听功能(假设有个叫scrollHandler的处理函数)
    window.addEventListener('scroll', throttle(scrollHandler, 300));

【参考内容】:

[1] CSDN - JS 节流与防抖

[2] 哔哩哔哩 - 课程分享