前端列表页大数据内存优化的思考

业务场景如下

最近自己的小程序接入了开源的菜谱接口(上个原型图),数据量上w,目前只是做了最简单的节流

代码如下 欢迎大佬们CR

js 复制代码
const loading = ref(false)
const finished = ref(false)

const getMoreFood = async () => {
 // 如果正在加载或已经加载完毕,直接返回
 if (loading.value || finished.value) return

 loading.value = true
 try {
   const resC = await getCookListById(categoryId.value, currentPage.value)
   // 兼容后端返回格式
   const list = Array.isArray(resC?.data?.data?.list) ? resC.data.data.list : []
   // 如果本次返回没有数据,说明到底了
   if (list.length === 0) {
     finished.value = true
     uni.showToast({
       title: '没有更多啦~',
       icon: 'none'
     })
   } else {
     cookList.value = cookList.value.concat(list)
     currentPage.value++ // 只有有数据时才自增页码
   }
   console.log('加载更多', cookList.value)
 } catch (e) {
   // 错误处理
   console.error('加载失败', e)
 } finally {
   loading.value = false
 }
}

但是问题也显而易见,用户如果不断下拉cookList会越来越大,所以带着问题去问了GPT,给出的一个方案如下

js 复制代码
const BUFFER = 2; // 缓存前后各 2 页
let currentPage = 1;
const cache = new Map(); // pageNo -> items[]

async function loadPage(page) {
  if (!cache.has(page)) {
    cache.set(page, (await fetchPage(page)).data);
  }
  currentPage = page;
  // 清理过期页
  for (let key of cache.keys()) {
    if (Math.abs(key - currentPage) > BUFFER) {
      cache.delete(key);
    }
  }
  return cache.get(page);
}

很好理解,BUFFER参数控制了缓存的页数也就对应列表的数量

那么我们来模拟下:

用户一开始请求page-1,loadPage函数缓存没有命中,去请求,然后返回数据

然后是page-2,page-3,请求page-4时候Math.abs(1 - 4) > BUFFER 会把page-1的数据清理掉

当用户往回滚动,page-2,page-3都命中缓存,page-1被清理了触发!cache.has(page)会请求一次且page-4会被清理掉

拓展

上述的内容讲的是基于滑动窗口的优化思路,下面给出 Pagination 分页的优化思路
LRU全称Least Recently Used,即最近最少使用页面置换算法,学过操作系统的肯定不陌生

js 复制代码
class LRUCache {
  constructor(maxPages, fetchPageFn) {
    this.maxPages = maxPages;
    this.cache = new Map();        // 按插入顺序维护
    this.fetchPage = fetchPageFn;  // 外部传入的分页请求函数: async (pageNo) => data[]
  }

  async get(pageNo) {
    // 1. 如果命中:刷新顺序,直接返回
    if (this.cache.has(pageNo)) {
      const data = this.cache.get(pageNo);
      // 拿出来再放回去,变成最新
      this.cache.delete(pageNo);
      this.cache.set(pageNo, data);
      return data;
    }

    // 2. 缓存未命中:去后端拉取
    const data = await this.fetchPage(pageNo);

    // 3. 如果容量已满,删最老的
    if (this.cache.size >= this.maxPages) {
      const oldestKey = this.cache.keys().next().value;
      this.cache.delete(oldestKey);
    }

    // 4. 放入缓存,并返回
    this.cache.set(pageNo, data);
    return data;
  }
}

// 用法示例
// 假设 fetchPageFromServer 是 async pageNo => [{ ... }] 的函数
const pager = new LRUCache(5, fetchPageFromServer);

async function loadAndRender(page) {
  // 这一行会自动命中则取缓存,未命中则请求并缓存
  const items = await pager.get(page);
  renderList(items);
}

代码还算好理解

解释下 this.cache.keys().next().value

上图重点是按顺序

所以 this.cache.keys().next().value 拿到最先插入也就是最旧的页面

综上就是我想记录的所有内容了,首先感叹AI真是一名好老师,可以少走很多弯路,欢迎大佬们给出建议,也可以一起讨论你们的开发小技巧

相关推荐
子兮曰3 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰3 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万3 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝3 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋3 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
晨米酱3 天前
AGENTS.md:Agent 的上下文策略层
面试·架构·agent
卡布鲁3 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
彧azz3 天前
Linux 环境下 Redis 学习总结:数据类型、持久化、锁、事务、主从与缓存问题
linux·redis·笔记·学习·面试
汉堡大王95273 天前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
梦想很大很大3 天前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端