荣耀基础服务App
80.64MB · 2026-04-16
2026 年,Andrej Karpathy 公开了 autoresearch。对我最有启发的地方,不是训练小模型这件事本身,而是它把研究组织成了一套有边界、有基线、有保留和放弃规则的持续实验机制。
这套思路很适合迁移到前端性能优化领域。相比一次性的性能专项,它更强调先固定实验环境、限制改动范围、统一记录结果,再让 agent 在明确规则下持续提出假设、验证假设、保留有效改动。
在这套方法的指引下,我把前端性能优化组织成一套“固定实验室环境、限制改动范围、小步实验、统一记录结果、只保留被证明有效的改动”的持续优化系统。
第一次落地这套前端性能实验方法时,只选:
推荐的上手方式:
不推荐第一次就做:
每轮实验只设一个主目标,推荐从下面选一个:
p75 LCPp75 INPp75 CLS推荐口径:
一个简单模板:
p75 INP可以用下面这张表快速判断一个指标适不适合做每轮实验的主目标:
| 判断维度 | 好的指标 | 坏的指标 |
|---|---|---|
| 和用户体验的关系 | 直接反映用户体感,例如页面是否出来得快、点击后是否有响应、页面是否会跳动 | 只反映局部技术状态,和用户实际感受距离较远 |
| 稳定性 | 在相同环境下重复测试,结果相对稳定,可比较 | 噪声很大,多跑几次就明显波动,分不清是改动生效还是环境干扰 |
| 可操作性 | 改动之后能较快看到变化,适合指导下一轮实验 | 反馈很慢,或者变化太间接,难以指导单轮决策 |
| 是否容易“刷分” | 指标变好通常意味着体验真的变好 | 很容易通过技巧把数字做漂亮,但用户体验并没有改善 |
| 团队共识成本 | 团队容易理解,能围绕它达成统一判断 | 每次都要解释它代表什么,为什么它重要 |
常见的坏指标有几类:
一个简单判断方法是:
LCPINPCLS在实验室中,必须先把这些条件固定住:
输出至少包含:
这里的作用不是替代线上数据,而是把实验反馈周期压缩到团队可持续迭代的程度。
如果只靠实验室环境,你会遇到两个问题:
所以每个准备晋级的实验都要经过线上真实用户数据确认,例如:
p75 指标简单理解:
推荐按风险分层开放。关键不是“列一个大清单”,而是把每层能碰什么、不能碰什么写清楚。
建议团队给 agent 一份明确边界:
一个简单模板:
允许修改:
- src/routes/product/**
- src/components/hero/**
- src/utils/loaders/**
禁止修改:
- src/domain/order/**
- src/tracking/**
- src/seo/**
- server/**
允许的改动类型:
- 资源加载策略
- 代码切分
- 图片与字体加载策略
禁止的改动类型:
- 改业务逻辑
- 改埋点协议
- 改 SEO 结构
- 改后端接口
下面按三层说明。
适合开放给 agent 的内容:
defer / async / 空闲时调度适合放开的原因:
代码示例 1:把第三方组件改成按需加载
import { lazy, Suspense } from "react";
const ReviewWidget = lazy(() => import("./ReviewWidget"));
export function ProductSidebar() {
return (
<aside>
<ProductSummary />
<Suspense fallback={null}>
<ReviewWidget />
</Suspense>
</aside>
);
}
代码示例 2:把首屏外图片改成原生懒加载
export function ProductGalleryImage({ src, alt }: { src: string; alt: string }) {
return (
<img
src={src}
alt={alt}
loading="lazy"
decoding="async"
width={640}
height={640}
/>
);
}
代码示例 3:把非关键第三方脚本延后到空闲阶段加载
function loadScript(src: string) {
const script = document.createElement("script");
script.src = src;
script.async = true;
document.head.appendChild(script);
}
if ("requestIdleCallback" in window) {
window.requestIdleCallback(() => loadScript("https://example.com/chat-widget.js"));
} else {
window.setTimeout(() => loadScript("https://example.com/chat-widget.js"), 2000);
}
适合开放给 agent 的内容:
这层收益更可能明显,但也更容易带来体验副作用,所以需要更严格的人工审查。
代码示例 1:把非关键区域延后挂载
import { useEffect, useState } from "react";
export function ProductPage() {
const [showRecommendations, setShowRecommendations] = useState(false);
useEffect(() => {
const timer = window.setTimeout(() => setShowRecommendations(true), 1200);
return () => window.clearTimeout(timer);
}, []);
return (
<>
<Hero />
<PriceBlock />
{showRecommendations ? <Recommendations /> : null}
</>
);
}
代码示例 2:把重计算拆到下一帧,减少一次长任务
function onFilterChange(nextValue: string) {
setKeyword(nextValue);
requestAnimationFrame(() => {
runHeavyFilter(nextValue);
});
}
代码示例 3:对频繁触发的逻辑做节流
function throttle<T extends (...args: any[]) => void>(fn: T, wait: number) {
let pending = false;
return (...args: Parameters<T>) => {
if (pending) return;
pending = true;
window.setTimeout(() => {
fn(...args);
pending = false;
}, wait);
};
}
const onScroll = throttle(() => {
updateVisibleCards();
}, 100);
window.addEventListener("scroll", onScroll, { passive: true });
适合谨慎开放的内容:
这层不建议一开始就开放给 agent,除非团队已经有稳定的验证链路和回滚机制。
代码示例 1:把整页从直接客户端渲染改成服务端下发关键内容
export async function ProductPageServer({ productId }: { productId: string }) {
const product = await getProduct(productId);
return (
<>
<Hero product={product} />
<PriceBlock product={product} />
<ClientRecommendations productId={productId} />
</>
);
}
代码示例 2:把全量客户端状态改成按页面拆分的局部状态
import { create } from "zustand";
type ProductPageState = {
selectedSkuId: string | null;
setSelectedSkuId: (skuId: string) => void;
};
export const useProductPageStore = create<ProductPageState>((set) => ({
selectedSkuId: null,
setSelectedSkuId: (skuId) => set({ selectedSkuId: skuId }),
}));
代码示例 3:把页面的一部分改成 islands 式延后激活
export default function ProductPage() {
return (
<>
<StaticHero />
<StaticSpecs />
<InteractiveReviewsIsland client:visible />
</>
);
}
建议每轮实验都记到统一表里,例如 results.tsv 或团队共享表格,至少包含:
id:实验编号commitpage_or_flownorth_star_metricnorth_star_beforenorth_star_afterbottom_line_statuslab_resultfield_resultstatusdescriptionrisk_notestatus 建议只有三种:
keepdiscardcrash一个样例:
| id | commit | page_or_flow | north_star_metric | before | after | status | description |
|---|---|---|---|---|---|---|---|
| exp-01 | a1b2c3d | product-detail | p75 INP | 280ms | 280ms | keep | baseline |
| exp-02 | b2c3d4e | product-detail | p75 INP | 280ms | 235ms | keep | defer third-party review widget |
| exp-03 | c3d4e5f | product-detail | p75 INP | 235ms | 238ms | discard | aggressive image preload changed waterfall |
| exp-04 | d4e5f6g | product-detail | p75 INP | 235ms | invalid | crash | hydration split caused event binding failure |
推荐的默认规则:
重点不是把所有实验都做成 keep,而是快速而诚实地淘汰无效方案。
建议团队在评审实验时,额外问四个问题:
如果答案偏负面,就算指标更好,也不一定要 keep。
推荐一个现实可行的节奏:
对多数团队来说,更适合的不是“让 agent 无限循环一整晚”,而是:
这套方法的正确打开方式,不是让人退出系统,而是让人把判断力集中在真正需要判断的地方。
下面这份模板适合给 agent 作为单轮实验指令:
你现在负责一轮前端性能实验,只能在允许的改动范围内行动。
目标页面:
- 商品详情页移动端
北极星指标:
- p75 INP
底线指标:
- 关键功能不能回归
- 错误率不能上升
- CLS 不能变差
允许改动范围:
- 第三方脚本加载策略
- 代码切分
- 图片与字体加载策略
不允许改动范围:
- 核心业务逻辑
- 埋点协议
- SEO 结构
工作流程:
1. 先读取 baseline 和最近几轮实验记录
2. 只提出一个实验假设
3. 修改代码并记录改动说明
4. 跑实验室测试并记录主指标与底线指标
5. 给出 keep / discard / crash 建议
6. 如果收益不明确或风险偏高,默认 discard
维护原则:
- 同等收益下优先更简单的方案
- 小收益但明显增加维护成本,不保留
这套方法真正的价值,不在于“用 AI 自动优化前端”,而在于让性能优化从“靠高手临场发挥”变成“团队可重复运行的实验机制”。
先把实验环境、改动边界和结果记录搭好,再让 agent 开始优化。