面试官:请你手写一下防抖节流

792 阅读8分钟

引言

在前端开发中,我们常常需要处理用户频繁触发的事件,这时防抖与节流就显得尤为重要。它们不仅能显著提升页面的性能,还能优化用户体验。当然,这也是面试过程中面试常考的一个问题,然而,许多人对这两个概念的理解还不够深入,容易混淆它们的使用场景。这次呆同学将详细解析防抖与节流的原理,手写实现,并结合实际应用场景,帮助你在面试中拿捏面试官。

防抖(Debounce)

  • 定义:防抖是一种在事件频繁触发时,确保只有最后一次触发的机制。在设置的延迟时间内,如果该事件再次被触发,计时器将重新开始计时,只有在这段时间内不再触发该事件时,才会执行最终的事件处理逻辑。

  • 适用场景

    1. 输入框实时搜索:防止用户在快速输入时频繁发送请求,只在用户停止输入后一段时间才发送最终请求。
    2. 窗口大小调整:在用户调整窗口大小时避免过多的计算和渲染,只在调整结束时执行最终的操作。
    3. 表单输入验证:防抖可以用来在用户停止输入一段时间后进行输入验证。这样可以避免每次输入时都触发验证,减少计算和网络请求。
    4. 用户行为分析:防抖可以用于分析用户的行为模式,例如在用户最后一次点击后进行分析,而不是在每次点击时都进行处理

节流(Throttle)

  • 定义:节流是一种控制事件触发频率的机制。无论事件触发多频繁,在设定的时间间隔内只允许一次事件处理。这意味着在指定时间间隔内,即使事件多次触发,也只会执行一次。

  • 适用场景

    1. 滚动事件:在用户滚动页面时限制触发事件的频率,避免频繁的 DOM 操作,提升性能。
    2. 窗口调整大小:在窗口调整大小的过程中限制计算或重新渲染的频率,减少不必要的资源消耗。
    3. 实时数据更新:节流可以用来在滚动、缩放等操作中定期更新实时数据。例如,在用户滚动页面时,节流可以限制数据更新的频率,防止频繁触发导致性能问题。
    4. 动画效果:节流可以应用于动画效果中,限制动画的更新频率,从而优化性能并确保平滑的动画体验。
    5. 频繁的用户操作:节流适用于需要持续响应用户操作的场景,如滚动加载、调整窗口大小等,确保在规定的时间间隔内执行操作。

防抖的实现

防抖函数用于控制事件的频繁触发,确保在一定时间内只有最后一次触发的操作会被执行。比如在用户点击按钮时,只有用户停止点击一段时间后,才会触发点击请求。

手写防抖函数

let btn = document.getElementById('btn');
btn.addEventListener('click', debounce(handle));

function handle(e) {
    console.log('点击触发', this, e);
}

// 防抖
function debounce(fn) {
    // 不能放在return中,因为这样的话每次调用都会重新创建一个time
    let timer = null;
    return function(...args) {
        let _this = this; // 保存当前的 this
        clearTimeout(timer);
        time = setTimeout(function() {
            fn.apply(_this, args); // 使用保存的 this 调用 fn
        }, 1000);
    }
}

这里我们通过给定时器取名为timer,是为了方便清除定时器,因为防抖就是在规定时间内如果用户继续操作,就需要重启定时器,那么在这之前就必须要清除上一个定时器。

补充:由于setTimout定时器函数中的this是指向全局的,且接收的回调函数格式是普通函数(如上述中所示),那么执行上下文中的this会丢失,所以需要先获取到上下文的this,然后使用保存的this调用fn

那么还有一种办法可以省略这一步骤,那便是将回调函数写成箭头函数,因为箭头函数中不存在this,如果箭头函数中写了this,那它代表的是箭头函数外层的函数的执行上下文,详细解释可以看我的这篇文章:this关键字

节流的实现

节流函数用于限制函数的执行频率。在规定的时间间隔内,函数只能被执行一次。例如,在用户连续点击时,限制点击事件的处理频率,以减少计算量。

手写节流函数

let btn = document.getElementById('btn');
btn.addEventListener('click', throttle(handle));

function handle(e) {
    console.log('点击触发', this, e);
}

// 节流
function throttle(fn) {
    let preTime = Data.now();
    return function(...args) {
        let curTime = Date.now();
        if (curTime - preTime >= 1000) { // 检查时间间隔
            preTime = curTime; // 更新上次执行时间
            fn.apply(this, args);  // 使用当前的 this 调用 fn
        }
    }
}

这里通过Data函数来获取时间戳,通过判断用户操作的时间间隔是否大于规定时间,如果大于规定时间就执行函数,因为节流的目的就是在规定的时间间隔内,函数只能被执行一次。

防抖和节流这两种技术,它们其实可以在更复杂的场景中发挥作用,并且在某些情况下可以配合使用。

防抖与节流的配合使用

在某些复杂的场景中,防抖和节流可以配合使用,以获得更好的性能和用户体验。例如:

  1. 复杂表单输入:

    当处理复杂表单输入时,可以使用防抖来处理输入验证或自动保存,避免每次输入时都进行处理。与此同时,可以使用节流来限制数据的提交频率,以防止过于频繁的提交请求。

  2. 滚动事件与数据加载:

    在实现无限滚动时,可以使用节流来限制滚动事件处理的频率,同时使用防抖来处理滚动停止后的数据加载或其他处理。

  3. 调整窗口大小和复杂布局:

    对于需要根据窗口大小调整布局的场景,可以使用节流来控制调整大小事件的处理频率,同时使用防抖来触发布局重新计算或重绘操作。

// 结合防抖与节流的示例
function combinedDebounceThrottle(fn, debounceDelay = 300, throttleDelay = 1000) {
    let debounceTimer = null;
    let preThrottleTime = 0;

    return function(...args) {
        const curThrottleTime = Date.now();

        // 防抖逻辑
        clearTimeout(debounceTimer);
        debounceTimer = setTimeout(() => {
            if (curTime - preThrottleTime >= throttleDelay) {
                fn.apply(this, args);
                preThrottleTime = curThrottleTime;
            }
        }, debounceDelay);
    };
}

// 使用示例
const handleScroll = combinedDebounceThrottle(() => {
    console.log('处理滚动事件');
}, 300, 1000);

window.addEventListener('scroll', handleScroll);

Lodash

在手写了防抖节流函数之后,我们对这两种技术有了比较深刻的理解,那么我们接下来还需要了解一种比较实用的工具库,它就叫做 Lodash。

Lodash 是一个非常流行的 JavaScript 工具库,其中包含了 debouncethrottle 方法,可以方便地实现防抖和节流。这些方法在处理高频事件时非常实用,而且它们的实现已经过了大量的优化,具有很好的性能和兼容性。

使用 Lodash 实现防抖

// 引入 lodash
import _ from 'lodash';

// 使用 lodash 的 debounce 方法
const handleDebounced = _.debounce(function() {
    console.log('防抖触发');
}, 1000);

// 绑定事件
button.addEventListener('click', handleDebounced);

使用 Lodash 实现节流

// 使用 lodash 的 throttle 方法
const handleThrottled = _.throttle(function() {
    console.log('节流触发');
}, 1000);

// 绑定事件
window.addEventListener('scroll', handleThrottled);

Lodash 导入方法

1. 通过 ES6 模块导入

如果使用的是现代前端构建工具(如 Webpack、Vite 等),可以使用 ES6 模块导入:

import debounce from 'lodash/debounce';
import throttle from 'lodash/throttle';

const handleDebounced = debounce(() => {
    console.log('防抖触发');
}, 1000);

const handleThrottled = throttle(() => {
    console.log('节流触发');
}, 1000);

2. 通过 Lodash 的完整包导入

如果你想导入整个 Lodash 库,可以这样做:

import _ from 'lodash';

const handleDebounced = _.debounce(() => {
    console.log('防抖触发');
}, 1000);

const handleThrottled = _.throttle(() => {
    console.log('节流触发');
}, 1000);

3. 通过 CDN 直接引入

如果你在没有构建工具的环境下使用,可以通过 CDN 直接引入:

<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>
<script>
    const handleDebounced = _.debounce(() => {
        console.log('防抖触发');
    }, 1000);

    const handleThrottled = _.throttle(() => {
        console.log('节流触发');
    }, 1000);

    document.getElementById('button').addEventListener('click', handleDebounced);
    window.addEventListener('scroll', handleThrottled);
</script>

4. 通过 Node.js 环境下的 CommonJS 模块引入

在 Node.js 或者使用 CommonJS 模块的环境中,可以这样引入:

const debounce = require('lodash/debounce');
const throttle = require('lodash/throttle');

const handleDebounced = debounce(() => {
    console.log('防抖触发');
}, 1000);

const handleThrottled = throttle(() => {
    console.log('节流触发');
}, 1000);

总结

今天我们深入探讨了防抖(Debounce)和节流(Throttle)这两种高频事件处理技术。防抖主要用于避免频繁触发的事件在短时间内多次执行,它确保只有在事件停止触发一段时间后才会执行指定的操作。节流则限制在一定时间内事件的执行次数,以控制事件处理的频率,避免在高频触发时对性能造成影响。

我们通过实际代码示例理解了这两种技术的实现原理,并讨论了它们的常见应用场景,如输入框实时搜索、滚动事件处理、窗口调整等。我们还探讨了在复杂场景中如何结合使用防抖与节流,以优化性能和用户体验。

这样你在面试中就不但能自信的手写出防抖节流函数,还能加入自己的理解,跟面试官表达自己的想法。