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

业务场景如下

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

相关推荐
We་ct32 分钟前
深度剖析浏览器跨域问题
开发语言·前端·浏览器·跨域·cors·同源·浏览器跨域
weisian1511 小时前
基础篇--概念原理-2-参数是什么?——从原理到实战,一篇讲透
面试·职场和发展·模型参数·7b和70b·参数=规则,不是原始数据
weixin_427771611 小时前
前端调试隐藏元素
前端
爱上好庆祝2 小时前
学习js的第五天
前端·css·学习·html·css3·js
AI人工智能+电脑小能手2 小时前
【大白话说Java面试题】【Java基础篇】第26题:Java的抽象类和接口有哪些区别
java·开发语言·面试
C澒2 小时前
IntelliPro 产研协作平台:基于 AI Agent 的低代码智能化配置方案设计与实现
前端·低代码·ai编程
一袋米扛几楼982 小时前
【Git】规范化协作:详解 GitHub 工作流中的 Issue、Branch 与 Pull Request 最佳实践
前端·git·github·issue
网络点点滴3 小时前
前端与后端的区别与联系
前端
EnCi Zheng3 小时前
M5-markconv自定义CSS样式指南 [特殊字符]
前端·css·python
kyriewen3 小时前
你的网页慢,用户不说直接走——前端性能监控教你“读心术”
前端·性能优化·监控