优化前端性能:深入理解防抖和节流

318 阅读5分钟

前言

在现代Web开发中,用户交互频繁且复杂,尤其是在处理用户输入或窗口调整等事件时,可能会触发大量的回调函数执行。这些密集的调用不仅消耗了不必要的计算资源,还可能导致用户体验下降。这个时候就体现出防抖(debounce)节流(throttling)的重要性了,防抖和节流对于我们前端的性能优化是一个必不可少的部分,也是大厂面试中经常碰到的问题。本文将用一个用户搜索的例子来一起探讨一下防抖和节流及它们的重要性。

什么是防抖和节流

  • 防抖:n秒后再执行该事件,如果在n秒内被重新触发,则重新开始计时。
  • 节流:n秒内只运行一次,若在n秒内重复触发,只生效一次

灵活运用这两种技术能减少事件处理程序的执行频率,从而提高应用的反应速度和效率。

用户搜索功能

先来看下不用防抖和节流的半成品,当我们输入字符时,我们希望能够实时显示匹配的结果,然而如果每次按键都立即发起网络请求来获取数据,那么这将导致过多的HTTP请求被发送出去,增加了服务器负载,并可能因为网络延迟影响用户的体验,再严重点,可能直接就把服务器搞崩了,搞不好第一天入职大厂就得删数据库提桶跑路了[doge]

下图是笔者没有添加防抖和节流的图片,简单查找个张三网络就发送了这么多请求。

image.png

添加防抖和节流

为了避免上面这种情况,我们可以使用防抖技术来限制搜索请求的频率,确保只在用户停止打字一段时间后才发出请求。下面是添加了防抖和节流的代码:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div>
    <label for="unDebounceInput">用户搜索</label>
    <input 
      type="text"
      id="unDebounceInput"
      placeholder="请输入要搜索的用户名字"
    >
  </div>
  <ul id="users"></ul>
  <script>
    const oUL = document.querySelector('#users');
    const oInput = document.getElementById('unDebounceInput');

    // 使用防抖技术优化搜索
    const debounceNameSearch = debounce(handleNameSearch, 500);
    oInput.addEventListener('keyup', debounceNameSearch);

    function handleNameSearch() {
      let value = oInput.value.trim();
      if (value === '') {
        oUL.innerHTML = '';
        return;
      }

      fetch('http://localhost:3001/users')
        .then(res => res.json())
        .then(users => {
          const filterUsers = users.filter(user => user.name.includes(value));
          oUL.innerHTML = filterUsers.map(user => `<li>${user.name}</li>`).join("");
        });
    }

    // 防抖函数
    function debounce(fn, delay) {
      let id;
      return function(...args) {
        clearTimeout(id);
        id = setTimeout(() => fn.apply(this, args), delay);
      };
    }
  </script>
</body>
</html>

在这个例子中,debounce 函数接收两个参数:需要优化的目标函数 fn 和等待的时间间隔 delay。它返回一个新的函数,在这个新函数内部设置了一个定时器,每当该函数被调用时都会清除之前的定时器并重新计时。这样可以保证只有在最后一次输入后的指定时间内没有新的输入发生时,才会真正调用目标函数。

后端支持:restful

为了让上述前端代码能够正常工作,我们需要一个后端服务来提供用户数据。这里我们选择了 json-server,因为它能快速搭建起一个基于 JSON 文件的数据源,模拟出 RESTful API 的行为模式。通过定义 db.json 文件中的数据结构,如 usersposts,就可以轻松创建、读取、更新或删除相应的资源。

在写后端代码时,我们要先通过npm init -y,npm i json-servernpm i这三个命令来初始化后端项目并安装json-server及初始化,笔者的文件夹如下图所示:

image.png

下面是一个简化的 db.json 示例:

{
  "users": [
    {
      "id": "1",
      "name": "张三"
    },
    {
      "id": "2",
      "name": "李四"
    },
    {
      "id": "3",
      "name": "王五"
    }
  ],
  "posts": [
    {
      "id": "1",
      "title": "学习APIFOX",
      "content": "Post 1 content",
      "userId": 1
    }
  ]
}

启动 json-server 后,可以通过访问类似 http://localhost:3001/users 的 URL 来获取所有用户的信息,或者访问 http://localhost:3001/users/:id 来获取特定用户的详情。这种设计遵循了 RESTful API 的原则,即通过 HTTP 方法(GET、POST、PUT/PATCH、DELETE)加上资源路径来操作数据库记录。

添加了防抖与节流后就请求次数就大量减少了,请看下图:

image.png

全栈视角下的防抖与节流

虽然我们在前端实现了防抖逻辑,但在某些情况下,也可能需要在后端实施类似的优化策略。比如,当多个客户端同时向服务器发送相似的查询请求时,可以利用节流机制来限制每秒钟最多处理多少个这样的请求,避免对数据库造成过大的压力。此外,还可以结合缓存技术,对于相同的查询结果直接返回缓存内容而非每次都重新查询。

在前后端分离的应用架构下,前端通常运行在像 live-server 这样的本地环境中,默认监听5500端口;而后端则由 json-server 管理,监听3001端口。两者通过明确的 API 接口进行通信,确保了系统的解耦性和可维护性。

小结

在不使用防抖和节流的情况下,频繁的用户交互(如滚动、调整窗口大小或输入查询)可能导致浏览器执行过多的昂贵操作,如DOM更新或网络请求。这会大量占用CPU和内存资源,导致设备发热和电量快速耗尽,尤其是在移动设备上。采用防抖和节流可以有效减少不必要的计算开销,提高应用程序的效率和能效比。

c224676594aae6804009e3fe521b122.jpg