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

业务场景如下

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

相关推荐
聪明的笨猪猪16 小时前
Java 集合 “Map(1)”面试清单(含超通俗生活案例与深度理解)
java·经验分享·笔记·面试
闲蛋小超人笑嘻嘻17 小时前
前端面试十四之webpack和vite有什么区别
前端·webpack·node.js
rggrgerj17 小时前
Vue3 组件完全指南代码
前端·javascript·vue.js
独行soc18 小时前
2025年渗透测试面试题总结-98(题目+回答)
网络·安全·web安全·adb·面试·渗透测试·安全狮
golang学习记19 小时前
从0死磕全栈之Next.js App Router动态路由详解:从入门到实战
前端
huangql52019 小时前
基于前端+Node.js 的 Markdown 笔记 PDF 导出系统完整实战
前端·笔记·node.js
在逃的吗喽19 小时前
Vue3新变化
前端·javascript·vue.js
yqwang_cn19 小时前
打造优雅的用户体验:自定义jQuery工具提示插件开发全解析
前端·jquery·ux
小Tomkk19 小时前
AI 提效:利用 AI 从前端 快速转型为UI/UX设计师和产品
前端·人工智能·ui
Demoncode_y20 小时前
Vue3中基于路由的动态递归菜单组件实现
前端·javascript·vue.js·学习·递归·菜单组件