百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

面试官问我,后端一次性返回十万条数据,前端应该怎么处理 ?

myzbx 2025-09-18 23:48 59 浏览

问题描述

  • 面试官:后端一次性返回10万条数据给你,你如何处理?
  • 我:歪嘴一笑,马上给后端发送一百万次请求,干蹦他的服务器,让他给爷哭!

问题考察点

  • 性能优化意识(能否识别出“10 万条数据”会导致性能问题?是否第一反应是优化处理方式?)
  • 浏览器渲染机制认知(是否理解 DOM 多、内存占用大、长任务对 UI 卡顿的影响?)
  • 数据处理策略(是否会用分页、分片、懒加载、虚拟滚动等数据加载/渲染策略?)
  • 项目实战经验(是否能结合实际业务讲解你曾用过的优化方案?)
  • 前后端协同思维(是否考虑跟后端协商分页/接口设计?)
  • 代码抽象能力(是否能设计合理的数据结构 / 缓存机制 / Worker / 节流方案?)

解决方案和思路

1.数据处理策略

  • 数据分片(分页展示):将大型树结构分解成多个小块,按需加载各个部分。
  • 虚拟列表:只渲染用户视口范围内的节点,减少DOM节点数量。
  • 懒加载:初始只加载第一层或前几层数据,用户展开节点时再动态请求子节点数据

2.前端优化技术

  • 数据扁平化:将树形结构转换为扁平结构,通过ID和parentID建立关系,便于管理和查询。
  • Web Worker:将数据处理逻辑放在后台线程中执行,避免阻塞主线程。
  • 缓存机制:使用浏览器存储(如IndexedDB、localStorage)缓存已加载的数据。

3.渲染优化

时间分片:使用requestAnimationFrame或setTimeout将渲染任务分割成小块,避免长时间阻塞主线程。

组件懒加载:结合React.lazy()和Suspense实现组件级别的懒加载。

节流与防抖:对滚动、展开等操作进行节流处理,减少重复渲染。

具体实现方案

虚拟列表可以看这篇文章手撕一个虚拟列表

数据分片(分页展示)

原理:将大数据集切分为小段,逐步加载,避免一次性渲染大量节点阻塞页面。

function renderChunk(data, renderFn, chunkSize = 100) {
  /**
   * @param {Array} data - 需要渲染的数据列表,例如 ['Item 1', 'Item 2', ...]
   * @param {Function} renderFn - 每条数据的渲染逻辑(回调函数),会对每一项调用:renderFn(item)
   * @param {number} chunkSize - 每次渲染的数据条数,默认是 100 条,可以根据实际情况调整
   */

  let index = 0; // 当前已渲染到数据列表的第几个元素

  // 内部函数:执行一次数据分片的渲染
  function nextChunk() {
    // 获取当前这一小块(分片)要渲染的数据:从 index 到 index + chunkSize
    const chunk = data.slice(index, index + chunkSize);

    // 对这段数据执行渲染逻辑(通过传入的 renderFn 回调)
    chunk.forEach(renderFn);

    // 更新索引,准备处理下一块数据
    index += chunkSize;

    // 如果还有数据没有渲染完,就使用 requestAnimationFrame 继续下一帧再渲染
    if (index < data.length) {
      // requestAnimationFrame 会在浏览器下一帧执行回调,避免阻塞 UI 渲染
      requestAnimationFrame(nextChunk);
    }
    // 如果所有数据已经渲染完了,递归终止
  }

  // 启动整个分片渲染流程
  nextChunk();
}

数据扁平化处理

原理:将嵌套结构改为对象映射结构,提升访问效率、便于缓存和更新。

/**
 * 将树形结构扁平化为以 id 为 key 的对象形式,保留父子关系
 * @param {Array} tree - 原始的树形结构数组(每个节点有 id 和 children)
 * @returns {Object} result - 扁平化后的对象
 */
function flattenTree(tree) {
  const result = {}; // 存储最终扁平化的结果对象

  /**
   * 递归处理每个节点,将其插入 result 中
   * @param {Object} node - 当前节点
   * @param {string|null} parentId - 当前节点的父节点 id,根节点为 null
   */
  function flatten(node, parentId = null) {
    const id = node.id; // 当前节点的唯一标识符

    // 将当前节点的信息添加到 result 中(排除 children 的嵌套结构)
    result[id] = {
      ...node, // 拷贝当前节点所有属性(包括 id、name 等)
      parentId, // 添加 parentId 字段,记录父节点信息
      children: node.children ? node.children.map(child => child.id) : [] // 替换 children 数组为子节点的 id 数组
    };

    // 如果当前节点有子节点,递归处理每个子节点
    if (node.children && node.children.length > 0) {
      node.children.forEach(child => flatten(child, id)); // 递归传入当前节点 id 作为子节点的父 id
    }
  }

  // 遍历树的每个根节点,启动递归扁平化
  tree.forEach(node => flatten(node));

  return result; // 返回最终的扁平化结果
}

扁平化后的数据更易于管理,可以快速查找和更新节点。

性能优化技巧

使用Web Worker处理数据

原理:将耗时计算任务交给子线程执行,避免阻塞 UI。

主线程代码(main.js)

// 创建一个新的 Web Worker 实例,worker.js 是 Worker 脚本的路径
const worker = new Worker('worker.js');

// 向 Worker 线程发送消息,请求处理大型树形数据
worker.postMessage({ type: 'PROCESS_TREE', data: largeTreeData });

// 监听 Worker 的返回消息
worker.onmessage = function(e) {
  // 判断消息类型是否为 "PROCESSED_TREE",即处理完成的数据
  if (e.data.type === 'PROCESSED_TREE') {
    // 使用处理后的数据来更新界面(避免主线程处理耗时任务造成卡顿)
    updateUI(e.data.result);
  }
};

Worker 线程代码(worker.js)

// 接收主线程发来的消息
self.onmessage = function(e) {
  // 判断消息类型是否为 "PROCESS_TREE"
  if (e.data.type === 'PROCESS_TREE') {
    // 调用处理函数,对大型树形数据进行处理
    const result = processLargeTree(e.data.data);

    // 将处理结果通过 postMessage 发送回主线程
    self.postMessage({ type: 'PROCESSED_TREE', result });
  }
};

// 用于处理大型树形结构的函数(这里是同步处理)
function processLargeTree(treeData) {
  // 在这里执行对大型树结构的复杂/耗时操作,比如深度遍历、节点标记、过滤等
  return processedData; // 注意:这是示意变量,你应在真实代码中生成它
}

时间分片渲染

原理:将任务拆分为小块分批执行,减少单次运算时间,避免卡顿。

// 时间分片处理函数:将大批任务分批处理,每帧处理一部分,避免一次性执行阻塞 UI
function timeSlice(tasks, fn, chunkSize = 5) {
  /**
   * @param {Array} tasks - 需要处理的任务列表(如 ['任务0', '任务1', ...])
   * @param {Function} fn - 每条任务的处理逻辑(回调函数)
   * @param {number} chunkSize - 每帧处理的任务数量,默认是 5,可根据实际性能设置
   */

  // 定义递归处理函数,每次只处理 chunkSize 个任务
  function next() {
    // 从 tasks 中取出前 chunkSize 个任务并从原数组中移除(原地修改)
    const chunk = tasks.splice(0, chunkSize);

    // 对当前这一批任务逐个执行处理函数
    chunk.forEach(fn);

    // 如果还有任务没处理完,则递归调用自身,放到下一帧继续执行
    if (tasks.length > 0) {
      requestAnimationFrame(next); // 下一帧再调用 next 继续处理剩下的任务
    }
    // 如果所有任务处理完毕,则递归终止
  }

  // 启动处理流程,在下一帧开始执行任务处理
  requestAnimationFrame(next);
}

使用IndexedDB缓存数据

存储树数据(storeTreeData)

// 将树形数据存入 IndexedDB 中,key 为 treeId
async function storeTreeData(treeId, treeData) {
  // 1. 打开数据库(异步操作)
  const db = await openDatabase();

  // 2. 创建一个事务,指定存储空间名为 'trees',权限为 'readwrite' 可读写
  const tx = db.transaction('trees', 'readwrite');

  // 3. 获取对象存储仓库(类似表)
  const store = tx.objectStore('trees');

  // 4. 将数据以 { id, data } 的结构插入或更新到对象仓库中
  await store.put({ id: treeId, data: treeData });

  // 5. 等待事务完成(注意 IndexedDB 是基于事务的,未提交前数据不会生效)
  await tx.complete;
}

读取树数据(getTreeData)

// 根据 treeId 从 IndexedDB 中读取树形数据
async function getTreeData(treeId) {
  // 1. 打开数据库
  const db = await openDatabase();

  // 2. 创建只读事务
  const tx = db.transaction('trees', 'readonly');

  // 3. 获取对象存储仓库
  const store = tx.objectStore('trees');

  // 4. 返回对应 key 的数据
  return await store.get(treeId);
}
// 打开名为 'TreeDataDB' 的 IndexedDB 数据库(版本号为 1)
function openDatabase() {
  return new Promise((resolve, reject) => {
    // 启动数据库打开请求
    const request = indexedDB.open('TreeDataDB', 1);
    
    // 如果是首次创建或版本升级,会触发此事件
    request.onupgradeneeded = e => {
      const db = e.target.result;

      // 创建名为 'trees' 的对象存储空间,主键为 'id'
      db.createObjectStore('trees', { keyPath: 'id' });
    };
    
    // 数据库成功打开时,返回 db 实例
    request.onsuccess = e => resolve(e.target.result);

    // 打开失败,返回错误信息
    request.onerror = e => reject(e.target.error);
  });
}

实现思路

  1. 数据库初始化
  2. 使用 indexedDB.open('TreeDataDB', 1) 打开或创建数据库;
  3. 如果是新数据库或版本变化,会触发 onupgradeneeded,此时新建一个名为 trees 的对象存储空间。
  4. 数据存储(storeTreeData)
  5. 调用 storeTreeData(treeId, treeData),将树数据通过事务写入数据库;
  6. 存储结构为 { id: treeId, data: treeData },其中 id 是主键。
  7. 数据读取(getTreeData)
  8. 通过树的唯一 treeId 读取 IndexedDB 中对应的缓存数据;
  9. 返回结果为 { id, data } 中的 data。

相关推荐

如何设计一个优秀的电子商务产品详情页

加入人人都是产品经理【起点学院】产品经理实战训练营,BAT产品总监手把手带你学产品电子商务网站的产品详情页面无疑是设计师和开发人员关注的最重要的网页之一。产品详情页面是客户作出“加入购物车”决定的页面...

怎么在JS中使用Ajax进行异步请求?

大家好,今天我来分享一项JavaScript的实战技巧,即如何在JS中使用Ajax进行异步请求,让你的网页速度瞬间提升。Ajax是一种在不刷新整个网页的情况下与服务器进行数据交互的技术,可以实现异步加...

中小企业如何组建,管理团队_中小企业应当如何开展组织结构设计变革

前言写了太多关于产品的东西觉得应该换换口味.从码农到架构师,从前端到平面再到UI、UE,最后走向了产品这条不归路,其实以前一直再给你们讲.产品经理跟项目经理区别没有特别大,两个岗位之间有很...

前端监控 SDK 开发分享_前端监控系统 开源

一、前言随着前端的发展和被重视,慢慢的行业内对于前端监控系统的重视程度也在增加。这里不对为什么需要监控再做解释。那我们先直接说说需求。对于中小型公司来说,可以直接使用三方的监控,比如自己搭建一套免费的...

Ajax 会被 fetch 取代吗?Axios 怎么办?

大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!今天给大家带来的主题是ajax、fetch...

前端面试题《AJAX》_前端面试ajax考点汇总

1.什么是ajax?ajax作用是什么?AJAX=异步JavaScript和XML。AJAX是一种用于创建快速动态网页的技术。通过在后台与服务器进行少量数据交换,AJAX可以使网页实...

Ajax 详细介绍_ajax

1、ajax是什么?asynchronousjavascriptandxml:异步的javascript和xml。ajax是用来改善用户体验的一种技术,其本质是利用浏览器内置的一个特殊的...

6款可替代dreamweaver的工具_替代powerdesigner的工具

dreamweaver对一个web前端工作者来说,再熟悉不过了,像我07年接触web前端开发就是用的dreamweaver,一直用到现在,身边的朋友有跟我推荐过各种更好用的可替代dreamweaver...

我敢保证,全网没有再比这更详细的Java知识点总结了,送你啊

接下来你看到的将是全网最详细的Java知识点总结,全文分为三大部分:Java基础、Java框架、Java+云数据小编将为大家仔细讲解每大部分里面的详细知识点,别眨眼,从小白到大佬、零基础到精通,你绝...

福斯《死侍》发布新剧照 &quot;小贱贱&quot;韦德被改造前造型曝光

时光网讯福斯出品的科幻片《死侍》今天发布新剧照,其中一张是较为罕见的死侍在被改造之前的剧照,其余两张剧照都是死侍在执行任务中的状态。据外媒推测,片方此时发布剧照,预计是为了给不久之后影片发布首款正式预...

2021年超详细的java学习路线总结—纯干货分享

本文整理了java开发的学习路线和相关的学习资源,非常适合零基础入门java的同学,希望大家在学习的时候,能够节省时间。纯干货,良心推荐!第一阶段:Java基础重点知识点:数据类型、核心语法、面向对象...

不用海淘,真黑五来到你身边:亚马逊15件热卖爆款推荐!

Fujifilm富士instaxMini8小黄人拍立得相机(黄色/蓝色)扫二维码进入购物页面黑五是入手一个轻巧可爱的拍立得相机的好时机,此款是mini8的小黄人特别版,除了颜色涂装成小黄人...

2025 年 Python 爬虫四大前沿技术:从异步到 AI

作为互联网大厂的后端Python爬虫开发,你是否也曾遇到过这些痛点:面对海量目标URL,单线程爬虫爬取一周还没完成任务;动态渲染的SPA页面,requests库返回的全是空白代码;好不容易...

最贱超级英雄《死侍》来了!_死侍超燃

死侍Deadpool(2016)导演:蒂姆·米勒编剧:略特·里斯/保罗·沃尼克主演:瑞恩·雷诺兹/莫蕾娜·巴卡林/吉娜·卡拉诺/艾德·斯克林/T·J·米勒类型:动作/...

停止javascript的ajax请求,取消axios请求,取消reactfetch请求

一、Ajax原生里可以通过XMLHttpRequest对象上的abort方法来中断ajax。注意abort方法不能阻止向服务器发送请求,只能停止当前ajax请求。停止javascript的ajax请求...