#性能
24 篇
把一座 Three.js 浮岛搬进静态站
浮岛页只下载自己那一份脚本。场景在运行时现算,Three.js 与整个三维世界都留在这一页的 chunk 里,首屏 HTML 里只有一个空舞台。
一千二百个实例:把花园压进几次绘制
草叶六百、花一百二、苔石八十五、灌木四十二 —— 花园的密度不靠减数量,靠 InstancedMesh 把同一种东西合并成一次绘制。
3D 的加载预算:分包、可见才加载、以及什么时候该停下
一个 3D 页面有三笔成本 —— 下载、首帧编译、每帧绘制。三笔都要单独设预算,任何一笔失控都会让「好看」变成「卡」。
大 O 的误用:常数和前提比阶更常决定成败
O(n log n) 的排序可能慢过 O(n²) 的插入排序,O(1) 的哈希表可能慢过数组线性扫。判断性能前先确认输入规模、常数和内存局部性。
防抖与节流:差别只在「谁决定何时执行」
两者都是限流,区别是防抖由最后一次调用决定执行时刻,节流由固定时间窗决定。选错的典型症状是拖拽跟手但松手才更新,或者输入框该等却每 200ms 就发一次。
游戏主循环:为什么必须用 delta time
每帧固定移动 5 像素,在 60Hz 屏幕上快一倍、在 144Hz 上快两倍半。把时间差乘进去,物理才与刷新率解耦;再加一个上限,切标签页回来才不会瞬移。
HTTP 缓存:Cache-Control 与 ETag 各管一半
Cache-Control 决定「要不要发请求」,ETag 决定「发出去之后能不能省下响应体」。把两者混谈是缓存问题的根源:改一个参数不会同时修好两侧。
内存布局:同样的算法,慢十倍往往是因为缓存
CPU 取一次内存要几百个周期,取整个缓存行只要一次。顺序访问与随机访问的差别往往大过算法阶数。
N+1 查询:ORM 最典型的性能陷阱
取 100 条记录再逐条查关联,就是 101 次查询。修法只有三种:预加载、批量查、或者干脆用一次 JOIN。
限流:令牌桶与漏桶的实际差别
令牌桶允许突发,漏桶把流量削成恒定速率。选错的典型症状是正常用户被限、或者限流形同虚设,取决于你的下游能不能吃突发。
滑动窗口:先定「窗口里维护什么」,再写循环
窗口能成立的前提是「窗口是否合法」随左右边界单调。先写下维护的状态与收缩条件,双指针的两层循环自然会摊还成 O(n)。
单调栈:把「下一个更大的数」从 O(n²) 拉回 O(n)
每个元素最多进栈一次、出栈一次,所以看起来是双层循环的写法其实是线性。认出「左右第一个比自己大/小」这个形状,就能套同一个模板。
数二进制里有多少个 1:从循环到 SWAR
popcount 有三种写法:按 1 的个数循环、查表、以及并行算位的 SWAR。三种复杂度差一个数量级,而 Rust 与 WebAssembly 已经把它做成了单条指令。
并查集:两行优化让每次操作几乎变成常数
路径压缩把树压平,按大小合并让树不会长高。两个都要,复杂度才落到反阿克曼函数上 —— 也就是说 10 亿次操作也不会慢。
Islands 不是 Web Components:两种「按需加载」根本不是一回事
Islands 在构建期决定哪里需要 JS、什么时候水合;Web Components 在运行时给浏览器一个自带行为的元素。把它们当成同一件事,会选错工具。
静态站的缓存头:带哈希的永久,HTML 一律不缓存
缓存策略只取决于文件名是否随内容变化,与文件大小无关。HTML 长缓存会让用户拿到旧入口、指向已删除的旧 chunk,是静态站最常见的线上白屏。
WebAssembly 值得用的场合,比宣传里少得多
WASM 的收益来自「计算密集 + 大批量数据 + 少的跨界调用」。把一个 O(n) 的 JS 循环改写成 WASM,通常只会得到更慢的加载和更难调的栈。
页头多高不该猜:ResizeObserver 实测回写 CSS 变量
导航换行、字体晚到、内容长度变化都会让元素高度和 CSS 里的常量对不上。ResizeObserver 观察元素本身,把实测值写回变量,比监听 resize 可靠。
纯静态站的全文搜索:构建期索引,运行时零后端
静态站做搜索不一定非得接 Algolia。把索引放到构建期生成,搜索完全跑在浏览器里,代价只有构建慢几秒。
用 Astro islands 把首屏 JS 压到零
从「异步引入」到「按需水合」,讲清楚三层懒加载分别解决什么问题,并给出这个站点里真实在跑的代码。
head 只有一个来源:SiteHead、JSON-LD 转义与 hreflang
三个 HTML 外壳共用一个组件输出全部 head 标签,数据在 server 层组装。以及一个必须记住的细节:JSON-LD 里的尖括号要转义成反斜杠 u003c。
静态站上线不是「把文件夹传上去」:缓存、压缩、以及诚实的 404
纯静态托管的坑几乎全在响应头上。哪些文件能永久缓存、哪些一秒都不能缓存、以及为什么静态站绝对不能做 SPA 式回退。
构建期分页:静态站的翻页为什么不该用查询参数
列表分页在构建时切成静态页,第 N 页是一个真实路由。没有客户端分页逻辑,也不用查询串,翻页就是纯链接跳转。
应用页不用 client:only:静态空壳加显式挂载
游戏与工具页是「空壳 + createApp」,不是 client:only。加载、成功、失败三态由我自己控制,重试按钮也是真的能按。
