面试官:防抖节流都不会吗 ?回去等通知吧😡

什么是防抖节流?

防抖(Debounce)

定义

防抖是一种限制函数调用频率的技术,它的核心思想是在一定时间内,只有最后一次调用函数才会被执行。如果在这个时间间隔内函数被多次调用,那么每次调用都会重置计时,直到最后一次调用后的指定时间结束,函数才会真正执行。

示例场景
  • 搜索框输入提示:在网页的搜索框中,当用户输入关键词时,通常会根据输入内容实时显示搜索提示。如果不使用防抖,用户每输入一个字符都会触发一次请求,这会导致大量不必要的请求发送到服务器,增加服务器压力。使用防抖后,只有当用户停止输入一段时间(例如 300 毫秒)后,才会发送请求获取搜索提示,减少了请求次数。
  • 窗口大小改变事件 :当浏览器窗口大小改变时,会触发 resize 事件。如果在这个事件处理函数中执行一些复杂的布局计算或重绘操作,不使用防抖的话,窗口大小改变过程中会频繁触发这些操作,影响性能。使用防抖可以确保只有在用户停止改变窗口大小一段时间后,才执行相应的计算和重绘。

节流(Throttle)

定义

节流也是一种限制函数调用频率的技术,它规定在一个固定的时间间隔内,函数只能被调用一次。如果在这个时间间隔内多次触发函数调用,只有第一次调用会被执行,后续的调用会被忽略,直到下一个时间间隔开始。

示例场景
  • 滚动加载更多:在一些网页的滚动加载场景中,当用户滚动页面到底部时,会触发加载更多数据的操作。如果不使用节流,用户快速滚动页面时会频繁触发加载请求,可能会导致数据重复加载或服务器压力过大。使用节流可以确保在一定时间内(例如 500 毫秒),只触发一次加载请求。
  • 按钮点击限制:对于一些需要避免用户频繁点击的按钮,如提交表单按钮、点赞按钮等,可以使用节流来限制按钮点击的响应频率,防止用户在短时间内多次点击造成不必要的操作。

防抖节流实验

未使用防抖节流

我们做下面的对比实验:

还没有使用防抖节流,代码如下:

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>防抖节流对比示例</title>
        <style>
            body {
                font-family: Arial, sans-serif;
            }

            label {
                display: block;
                margin-bottom: 5px;
            }

            input {
                margin-bottom: 10px;
                padding: 5px;
            }
        </style>
    </head>

    <body>
        <h2>未使用防抖节流</h2>
        <label for="originalInput">搜索框:</label>
        <input type="text" id="originalInput" placeholder="输入内容" />

        <h2>使用防抖</h2>
        <label for="debounceInput">搜索框:</label>
        <input type="text" id="debounceInput" placeholder="输入内容" />

        <h2>使用节流</h2>
        <label for="throttleInput">搜索框:</label>
        <input type="text" id="throttleInput" placeholder="输入内容" />

        <script>
            //没有防抖节流
            function handleOriginalInput() {
                console.log("未使用防抖节流,触发搜索请求,输入内容为:", this.value);
            }
            const originalInput = document.getElementById("originalInput");
            originalInput.addEventListener("input", handleOriginalInput); 

            // todo : 防抖函数
                         
            // todo : 节流函数
           
        </script>
    </body>
</html>

使用防抖

在上面 html 添加如下代码:

javascript 复制代码
function debounce(func, delay) {
  let timer;
  return function () {
    const context = this;
    const args = arguments;
    clearTimeout(timer);
    timer = setTimeout(() => {
      func.apply(context, args);
    }, delay);
  };
}
const debounceInput = document.getElementById("debounceInput");
// 应用防抖函数到输入框的 input 事件
const debouncedInputHandler = debounce(handleDebounceInput, 500);
debounceInput.addEventListener("input", debouncedInputHandler);

这段代码实现了一个防抖函数,下面详细解释为什么要这么写:

整体思路

防抖函数的核心目的是在一定时间内,只有最后一次调用函数才会被执行。如果在这个时间间隔内函数被多次调用,那么每次调用都会重置计时,直到最后一次调用后的指定时间结束,函数才会真正执行。为了实现这个功能,需要借助闭包和定时器来完成。

代码逐行解释

1. 函数定义和参数
javascript 复制代码
function debounce(func, delay) {
  • debounce 是一个高阶函数,它接受两个参数:
    • func:需要进行防抖处理的原始函数。
    • delay:防抖的时间间隔,单位为毫秒。
2. 声明定时器变量
javascript 复制代码
  let timer;
  • timer 用于存储定时器的 ID。由于 timer 是在 debounce 函数内部声明的,它会形成一个闭包,使得在返回的匿名函数中可以访问和修改这个变量。
3. 返回一个新函数
javascript 复制代码
  return function () {
  • debounce 函数返回一个新的匿名函数,这个新函数包含了防抖的逻辑。当调用 debounce 函数时,实际上是在返回这个新函数,后续调用这个新函数时,就会触发防抖处理。
4. 保存上下文和参数
javascript 复制代码
    const context = this;
    const args = arguments;
  • context:保存当前的执行上下文(this 值),确保在延迟执行 func 时,this 的指向不变。
  • args:保存调用新函数时传递的参数,以便在延迟执行 func 时可以将这些参数传递给它。
5. 清除定时器
javascript 复制代码
    clearTimeout(timer);
  • 每次调用新函数时,先清除之前设置的定时器。这是防抖的关键步骤,如果在延迟时间内再次调用了新函数,就会清除之前的定时器,重新开始计时。
6. 设置新的定时器
javascript 复制代码
    timer = setTimeout(() => {
      func.apply(context, args);
    }, delay);
  • 使用 setTimeout 函数设置一个新的定时器,在 delay 毫秒后执行 func 函数。
  • func.apply(context, args):使用 apply 方法调用 func 函数,并将之前保存的执行上下文 context 和参数 args 传递给它,确保 func 函数在正确的上下文和参数下执行。

通过这种方式,每次调用新函数时都会重置定时器,只有在最后一次调用后的 delay 毫秒内没有再次调用,定时器才会正常执行,从而实现了防抖的效果。

使用节流

在 html 中添加如下代码:

javascript 复制代码
function throttle(func, delay) {
  let timer = null;
  return function () {
    const context = this;
    const args = arguments;
    if (!timer) {
      func.apply(context, args);
      timer = setTimeout(() => {
        timer = null;
      }, delay);
    }
  };
}
const throttleInput = document.getElementById("throttleInput");
// 应用节流函数到输入框的 input 事件
const throttledInputHandler = throttle(handleThrottleInput, 1000);
throttleInput.addEventListener("input", throttledInputHandler);

这段代码实现了一个节流函数,下面为你详细解释其实现原理和各部分代码的作用。

节流函数的概念

节流是一种限制函数调用频率的技术,它规定在一个固定的时间间隔内,函数只能被调用一次。如果在这个时间间隔内多次触发函数调用,只有第一次调用会被执行,后续的调用会被忽略,直到下一个时间间隔开始。

代码详细解释

1. 函数定义和参数
javascript 复制代码
function throttle(func, delay) {
  • throttle 是一个高阶函数,它接收两个参数:
    • func:需要进行节流处理的原始函数,即你想要限制其调用频率的函数。
    • delay:节流的时间间隔,单位为毫秒。这个时间间隔决定了在多长时间内,func 函数只能被调用一次。
2. 声明定时器变量
javascript 复制代码
  let timer = null;
  • timer 用于存储定时器的 ID。初始值设为 null,表示当前没有正在计时的定时器。由于 timer 是在 throttle 函数内部声明的,它会形成一个闭包,使得在返回的匿名函数中可以访问和修改这个变量。
3. 返回一个新函数
javascript 复制代码
  return function () {
  • throttle 函数返回一个新的匿名函数。当调用 throttle 函数时,实际上是在返回这个新函数,后续调用这个新函数时,就会触发节流处理。
4. 保存上下文和参数
javascript 复制代码
    const context = this;
    const args = arguments;
  • context:保存当前的执行上下文(this 值)。因为在 JavaScript 中,函数内部的 this 指向可能会发生变化,通过保存 this 的值,可以确保在后续调用 func 函数时,this 的指向不变。
  • args:保存调用新函数时传递的参数。使用 arguments 对象可以获取所有传递给当前函数的参数,以便在调用 func 函数时将这些参数传递给它。
5. 判断是否可以执行函数
javascript 复制代码
    if (!timer) {
  • 检查 timer 是否为 null。如果 timernull,说明当前没有正在计时的定时器,即当前处于可以执行 func 函数的状态。
6. 执行函数并设置定时器
javascript 复制代码
      func.apply(context, args);
      timer = setTimeout(() => {
        timer = null;
      }, delay);
  • func.apply(context, args):使用 apply 方法调用 func 函数,并将之前保存的执行上下文 context 和参数 args 传递给它,确保 func 函数在正确的上下文和参数下执行。
  • setTimeout(() => { timer = null; }, delay):设置一个定时器,在 delay 毫秒后将 timer 重置为 null。这意味着在 delay 时间内,timer 不再为 null,后续调用新函数时,由于 if (!timer) 条件不满足,func 函数不会被执行,从而实现了节流的效果。

通过上述代码,throttle 函数实现了对 func 函数调用频率的限制。在 delay 时间间隔内,func 函数只能被调用一次,从而避免了函数被频繁调用,减少了不必要的计算和资源消耗,提高了性能。

真枪实弹

做一个前后端的实验来体验一下,防抖节流的操作。

前端

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>防抖节流前后端对比示例</title>
    <style>
        body {
            font-family: Arial, sans-serif;
        }

        label {
            display: block;
            margin-bottom: 5px;
        }

        input {
            margin-bottom: 10px;
            padding: 5px;
        }
    </style>
</head>

<body>
    <h2>未使用防抖节流</h2>
    <label for="originalInput">搜索框:</label>
    <input type="text" id="originalInput" placeholder="输入内容">

    <h2>使用防抖</h2>
    <label for="debounceInput">搜索框:</label>
    <input type="text" id="debounceInput" placeholder="输入内容">

    <h2>使用节流</h2>
    <label for="throttleInput">搜索框:</label>
    <input type="text" id="throttleInput" placeholder="输入内容">

    <script>
        // 防抖函数
        function debounce(func, delay) {
            let timer;
            return function () {
                const context = this;
                const args = arguments;
                clearTimeout(timer);
                timer = setTimeout(() => {
                    func.apply(context, args);
                }, delay);
            };
        }

        // 节流函数
        function throttle(func, delay) {
            let timer = null;
            return function () {
                const context = this;
                const args = arguments;
                if (!timer) {
                    func.apply(context, args);
                    timer = setTimeout(() => {
                        timer = null;
                    }, delay);
                }
            };
        }

        // 发送搜索请求的函数
        function sendSearchRequest(inputId) {
            const input = document.getElementById(inputId);
            const keyword = input.value;
            fetch(`/search?keyword=${encodeURIComponent(keyword)}`)
              .then(response => response.json())
              .then(data => {
                    console.log(`来自 ${inputId} 的搜索结果:`, data);
                })
              .catch(error => {
                    console.error('请求出错:', error);
                });
        }

        // 未使用防抖节流时的处理函数
        const originalInput = document.getElementById('originalInput');
        originalInput.addEventListener('input', function () {
            sendSearchRequest('originalInput');
        });

        // 防抖后的处理函数
        const debounceInput = document.getElementById('debounceInput');
        const debouncedSearch = debounce(() => sendSearchRequest('debounceInput'), 500);
        debounceInput.addEventListener('input', debouncedSearch);

        // 节流后的处理函数
        const throttleInput = document.getElementById('throttleInput');
        const throttledSearch = throttle(() => sendSearchRequest('throttleInput'), 300);
        throttleInput.addEventListener('input', throttledSearch);
    </script>
</body>

</html>

后端

javascript 复制代码
const express = require('express');
const app = express();
const port = 3000;

// 模拟搜索接口
app.get('/search', (req, res) => {
  const keyword = req.query.keyword;
  console.log(`收到搜索请求,关键词: ${keyword}`);
  // 模拟返回搜索结果
  const result = {
    message: `搜索结果为关于 ${keyword} 的内容`
  };
  res.json(result);
});

app.use(express.static('.'));

app.listen(port, () => {
  console.log(`服务器运行在 http://localhost:${port}`);
});
相关推荐
好_快22 分钟前
Lodash源码阅读-nth
前端·javascript·源码阅读
maybe啊27 分钟前
js 使用 Web Workers 来实现一个精确的倒计时,即使ios手机锁屏或页面进入后台,倒计时也不会暂停。
开发语言·前端·javascript
好_快28 分钟前
Lodash 源码阅读-baseNth
前端·javascript·源码阅读
好_快31 分钟前
Lodash源码阅读-join
前端·javascript·源码阅读
子洋32 分钟前
Chroma+LangChain:让AI联网回答更精准
前端·人工智能·后端
好_快33 分钟前
Lodash源码阅读-isIndex
前端·javascript·源码阅读
好_快36 分钟前
Lodash源码阅读-reverse
前端·javascript·源码阅读
Gazer_S2 小时前
【基于 SSE 协议与 EventSource 实现 AI 对话的流式交互】
前端·javascript·人工智能·交互
银迢迢3 小时前
如何创建一个Vue项目
前端·javascript·vue.js
几何心凉6 小时前
如何解决Vue组件间传递数据的问题?
前端·javascript·vue.js