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

业务场景如下

最近自己的小程序接入了开源的菜谱接口(上个原型图),数据量上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真是一名好老师,可以少走很多弯路,欢迎大佬们给出建议,也可以一起讨论你们的开发小技巧

相关推荐
一只小灿灿13 分钟前
前端计算机视觉:使用 OpenCV.js 在浏览器中实现图像处理
前端·opencv·计算机视觉
前端小趴菜0525 分钟前
react状态管理库 - zustand
前端·react.js·前端框架
zhuiQiuMX44 分钟前
字节面试手撕中等题但还没做出来
面试
Jerry Lau1 小时前
go go go 出发咯 - go web开发入门系列(二) Gin 框架实战指南
前端·golang·gin
我命由我123451 小时前
前端开发问题:SyntaxError: “undefined“ is not valid JSON
开发语言·前端·javascript·vue.js·json·ecmascript·js
0wioiw01 小时前
Flutter基础(前端教程③-跳转)
前端·flutter
落笔画忧愁e1 小时前
扣子Coze纯前端部署多Agents
前端
海天胜景1 小时前
vue3 当前页面方法暴露
前端·javascript·vue.js
GISer_Jing2 小时前
前端面试常考题目详解
前端·javascript
Boilermaker19923 小时前
【Java EE】SpringIoC
前端·数据库·spring