[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"article-virtual-list-is-not-enough":3,"article-neighbors-virtual-list-is-not-enough":17,"article-related-virtual-list-is-not-enough":37},{"id":4,"slug":5,"title":6,"excerpt":7,"category":8,"tags":9,"content":13,"publishedAt":14,"readingMinutes":15,"published":16},31,"virtual-list-is-not-enough","前端性能优化：虚拟列表真实适用场景与完整优化链路","列表卡顿别急着上虚拟列表，先排查网络、数据、渲染整条链路，最后才是它。","工程实践",[10,11,12],"Vue","虚拟列表","性能","前端列表一卡顿，很多人的第一反应就是\"上虚拟列表\"。我之前也这么干过，结果问题没解决，反而把代码搞复杂了。后来才明白，虚拟列表是渲染层的兜底方案，不是性能优化的万能药。\n\n先说清楚列表卡顿的根源。它从来不是一个原因，而是整条链路都有问题。\n\n### 卡顿是整条链路的问题\n\n网络层，一次接口返回了几千条数据；数据层，没有分页、没有按需过滤；渲染层，几千个 DOM 节点堆在页面上，一滚动就疯狂重排重绘；逻辑层，每个列表项里还塞着复杂的计算和无效的监听。四层问题叠加，主线程直接被拖垮，页面自然卡成 PPT。\n\n要理解渲染层的问题，得先懂浏览器的渲染管线。DOM 变动会触发\"重排\"（重新计算布局）和\"重绘\"（重新画像素），列表滚动时如果每个节点都在变，浏览器要不断重新计算几千个节点的布局，主线程就堵死了。所以\"减少 DOM 节点数量\"才是治本方向，虚拟列表就是干这个的。\n\n这里再补充一个概念：浏览器渲染是\"主线程\"干的活，JS 执行、布局、绘制都在主线程上排队。一旦主线程被长任务占满，页面就卡。所以性能优化的本质，是\"减少主线程的工作量\"——要么减少 DOM 节点，要么减少无效计算，要么把耗时操作丢给 Web Worker。\n\n### 优化的正确顺序：从根源到兜底\n\n所以我现在的优化思路是\"从上往下，从根源到兜底\"。\n\n第一步先治网络层：接口必须分页。`GET \u002Farticles?page=1&size=20`，后端限制最大 pageSize，别让一次请求返回几千条，这是治本。\n\n第二步治数据层：前端按需过滤字段，别把全部字段都塞进组件里。接口返回的字段越多，前端处理越慢，能精简就精简。\n\n第三步治逻辑层：把模板里的复杂函数挪进计算属性，长列表每行只绑定必要的数据，多余的 watcher 和事件监听该销毁就销毁。Vue 的响应式系统里，每个 watcher 都是开销，列表里的每一项都绑一堆监听，性能自然崩。这里还有个细节：列表项组件尽量用 `v-memo` 或稳定的 key，减少不必要的重新渲染。\n\n最后才轮到渲染层：如果数据量大到实在没法分页（比如日志流、实时消息、未分页的监控数据），再考虑虚拟列表。\n\n### 虚拟列表怎么用才对\n\n虚拟列表的原理是可视区 DOM 复用：只渲染当前能看到的那些节点，滚出去的销毁，让常驻 DOM 数量维持在几十个，从而解决海量节点的渲染问题。它确实好用，但使用场景很挑——只适合那种\"不可分页、数据量巨大、持续滚动\"的长列表。普通分页表格、几十条数据的小列表，你上虚拟列表纯属过度优化，除了增加复杂度没任何好处。\n\n用虚拟列表还有个容易翻车的点：行高不固定。我用的方案是维护一个 `Map` 记录每行实测高度，用 `ResizeObserver` 监听高度变化实时更新总偏移；筛选数据后还要重新计算起始索引，不能直接复用原来的 index，不然滚动位置就乱了。另外虚拟列表和滚动事件要配合好，最好用 `requestAnimationFrame` 节流滚动更新，不然滚动会掉帧。\n\n固定行高的虚拟列表实现起来简单，但内容不固定就麻烦——所以绝大多数真实项目都需要\"动态行高\"方案，这也是虚拟列表最容易踩坑的地方。还有防抖（debounce）和节流（throttle）的区别也要分清：防抖是\"停下才执行\"，适合搜索；节流是\"固定频率执行\"，适合滚动。\n\n### 补充两个思路\n\n除了虚拟列表，还有两个前端优化思路值得提：一个是\"懒渲染\"，配合 `IntersectionObserver`，只有当元素进入视口时才真正渲染，适合瀑布流这类场景；另一个是\"骨架屏\"，在数据加载时先渲染占位框架，避免白屏闪烁，让用户感知\"内容在路上\"而不是\"页面卡了\"。\n\n说到底，性能优化是系统工程，虚拟列表只是最后一环的兜底。把该分页的分页、该精简的精简，绝大多数卡顿根本轮不到用虚拟列表。先排查整条链路，再决定要不要上大招，这才是正确的姿势。\n","2025-12-10T02:00:00Z",6,true,{"prev":18,"next":28},{"id":19,"slug":20,"title":21,"excerpt":22,"category":8,"tags":23,"publishedAt":27,"readingMinutes":15},30,"frontend-security-is-not-ui-permission","前端权限安全：为什么仅隐藏 DOM 是无效安全","前端隐藏按钮只是体验优化，安全必须全部下沉到服务端，否则就是给攻击者留后门。",[24,25,26],"前端安全","权限","接口设计","2025-10-15T02:00:00Z",{"id":29,"slug":30,"title":31,"excerpt":32,"category":8,"tags":33,"publishedAt":36,"readingMinutes":15},24,"vue-request-cache-implementation","Vue 全局请求缓存工程级实现、过期策略与脏数据规避","给 Vue 项目加全局缓存时踩过的坑：过期策略、脏数据、并发去重，一篇讲清楚。",[10,34,35,12],"TypeScript","缓存","2026-01-25T02:00:00Z",[38,48,58],{"id":39,"slug":40,"title":41,"excerpt":42,"category":8,"tags":43,"publishedAt":47,"readingMinutes":15},1,"build-a-reliable-personal-site","个人网站工程架构与长期可维护设计","这个站从零搭起来，分享我用 Nuxt 加 Spring Boot 把个人项目做成敢一直改下去的经过。",[44,45,46],"Nuxt","Spring Boot","部署","2026-07-20T02:00:00Z",{"id":49,"slug":50,"title":51,"excerpt":52,"category":8,"tags":53,"publishedAt":57,"readingMinutes":15},32,"personal-site-feature-bloat","个人项目如何规避「功能废墟」工程问题","功能越加越多，项目却越来越难改，最后重构比重写还贵——这是我踩过的\"功能废墟\"坑。",[54,55,56],"个人网站","工程管理","维护","2026-05-15T02:00:00Z",{"id":29,"slug":30,"title":31,"excerpt":32,"category":8,"tags":59,"publishedAt":36,"readingMinutes":15},[10,34,35,12]]