
P-OPT1:<script> 放在 header 和 body 底部的区别?
| 位置 | 区别 |
|---|---|
| header 中 | 浏览器遇到 <script> 会同步加载并执行,阻塞后面 DOM 解析(脚本可能 document.write 改变 DOM),白屏时间长。 |
| body 底部 | DOM 解析完成后才加载脚本,页面元素先显示出来,用户感知更快。 |
进阶方案:
<!-- defer:并行下载,DOMContentLoaded 前执行 -->
<script src="app.js" defer></script>
<!-- async:并行下载,下载完立即执行(不保证顺序) -->
<script src="analytics.js" async></script>
<!-- type="module":默认 defer 行为 -->
<script type="module" src="app.js"></script>推荐:
- 第三方库用
defer(依赖顺序可控); - 独立统计脚本用
async(执行越早越好); - 应用入口用
defer。
P-OPT2:前端性能优化指标有哪些?怎么检测?
核心指标(Core Web Vitals):
| 指标 | 说明 | 优秀阈值 |
|---|---|---|
| FCP(First Contentful Paint) | 首次内容渲染 | < 1.8s |
| LCP(Largest Contentful Paint) | 最大内容渲染 | < 2.5s |
| FID(First Input Delay) | 首次输入延迟 | < 100ms |
| INP(Interaction to Next Paint) | 交互到下一帧 | < 200ms |
| CLS(Cumulative Layout Shift) | 累积布局偏移 | < 0.1 |
| TTI(Time to Interactive) | 可交互时间 | - |
| TBT(Total Blocking Time) | 总阻塞时间 | < 200ms |
检测工具:
- Lighthouse:综合性能评分;
- WebPageTest:多地点、多浏览器、多网络环境测试;
- Chrome DevTools Performance:录制运行时性能;
- PerformanceObserver API:业务中上报性能数据。
// 上报 LCP / FID / CLS
new PerformanceObserver(list => {
for (const entry of list.getEntries()) {
console.log(entry.name, entry.startTime);
sendMetric({ name: entry.name, value: entry.startTime });
}
}).observe({ type: 'largest-contentful-paint', buffered: true });P-OPT3:SPA 首屏加载速度慢怎么解决?
与 V14 互补,从性能指标角度补充:
- 骨架屏 / Loading:转移注意力,减少感知等待时间;
- 路由级代码分割:按路由懒加载,初始包体积减少 50%+;
- CDN 加速:静态资源走 CDN,启用 HTTP/2;
- 资源预加载:
<link rel="preload" href="critical.css">; - 预获取数据:路由切换前预取下一页数据;
- SSR / SSG:首屏 HTML 服务端生成,TTI 提升明显;
- 开启 gzip / brotli 压缩:减少 70%+ 体积;
- Web Vitals 监控:上线后持续优化。
P-OPT4:用 CSS 提升页面性能
减少 CSS 体积:
- 压缩合并(CSSNano);
- 移除未使用样式(PurgeCSS / UnCSS);
- CSS-in-JS 会增加运行时开销,需谨慎。
- 关键 CSS 内联:首屏所需 CSS 放在
<style>中,其余异步加载。 - 避免昂贵选择器:避免深层后代选择器(如
.a .b .c .d),浏览器从右到左匹配。 - 避免 @import:串行加载。
- CSS Containment:
contain: layout / paint / size限制重绘范围。 - will-change:提示浏览器提前优化,但不能滥用(会消耗 GPU 内存)。
- GPU 加速属性:
transform/opacity触发 GPU 合成,跳过重绘重排。
P-OPT5:站点内的图片性能优化
选择合适格式:
- WebP:体积比 JPEG/PNG 小 25-35%;
- AVIF:体积更小,压缩比优于 WebP;
- SVG:适合图标、插画。
按需加载:
- 原生
loading="lazy"; - 滚动到视口附近才加载;
- 占位图 + 模糊到清晰过渡。
- 原生
响应式图片:
<img srcset="small.jpg 480w, medium.jpg 800w, large.jpg 1200w" sizes="(max-width: 600px) 480px, 800px" src="medium.jpg" />- CDN + 图片处理:使用
?x-oss-process=image/resize,w_800等 CDN 参数。 - 压缩与裁剪:去除 EXIF、合理质量(75-85% 肉眼难辨)。
- 雪碧图:HTTP/2 下作用减弱。
- 避免 Layout Shift:永远设置
width/height属性。
P-OPT6:虚拟 DOM 一定更快吗?
不一定!
虚拟 DOM 适用场景:DOM 操作复杂、组件数量中等(几十到几百)。
更快的场景:
- 直接 DOM 操作:极简单页面,框架开销大于收益;
- 细粒度响应式(Vue / SolidJS):精确更新,零 VNode 开销;
- WebGL / Canvas:不走 DOM。
虚拟 DOM 的优势不是“更快”,而是“提供合理性能 + 开发体验”:
- 避免手动管理 DOM 状态;
- 提供跨平台能力(React Native、SSR);
- 批量更新减少 DOM 操作次数。
本质:是用 JS 计算换 DOM 操作,开发效率提升远大于性能损失。
P-OPT7:有些框架不用虚拟 DOM,性能也不错是为什么?
原因:
- 编译期优化:Svelte / SolidJS 在编译阶段将组件编译为命令式 DOM 操作代码,无需运行时 VNode 比较。
- 细粒度响应式:Vue / SolidJS 的响应式系统能精确追踪到组件内具体属性变化,只更新必要的 DOM 节点。
- 跳过 VNode 层:直接调用 DOM API,省去 diff 计算与对象创建开销。
举例:
- SolidJS:类似 React 的 JSX,但编译产物是“订阅-更新”函数;
- Svelte:编译为高度优化的 vanilla JS;
- Vue 3 + Vapor:Vue 3 的无 VNode 模式。
总结:没有 VNode 也能做到高效,关键是“精准更新”。
P-OPT8:几百个函数需要执行,怎么优化页面?
方案:
Web Worker:把 CPU 密集任务放到后台线程,避免主线程卡顿。
const worker = new Worker('worker.js'); worker.postMessage(data); worker.onmessage = e => console.log(e.data);任务切片 + 调度器:
function chunk(tasks: Function[], chunkSize = 50) { let i = 0; function next() { const slice = tasks.slice(i, i + chunkSize); i += chunkSize; slice.forEach(fn => fn()); if (i < tasks.length) requestIdleCallback(next); } next(); }- requestIdleCallback / requestAnimationFrame:在浏览器空闲时执行。
- 防抖节流:高频触发合并处理。
- WebAssembly:计算密集型(图形、加密)场景。
P-OPT9:png8、png16、png32 的区别,png 的特点
| 类型 | 颜色数 | 透明度 | 适用 |
|---|---|---|---|
| PNG8 | 256 色 | 不支持 | 简单图标、表情 |
| PNG24 | 约 1600 万色 | 不支持 | 复杂图片、照片 |
| PNG32 | 约 1600 万色 | 8 位 alpha | 需透明背景的复杂图片 |
PNG 特点:
- 无损压缩,质量高;
- 支持 alpha 透明;
- 文件体积通常比 JPEG 大,不适合照片(应选 JPEG / WebP);
- 适合 UI 切图、Logo、图标。
P-OPT10:页面加载过程中,JS 文件一定会阻塞 DOM 解析吗?
不一定!
| 加载方式 | 阻塞情况 |
|---|---|
普通 <script src="..."> | 阻塞:加载并同步执行 |
<script defer> | 不阻塞(并行下载,DOMContentLoaded 前执行) |
<script async> | 不阻塞(并行下载,下载完立即执行) |
<script type="module"> | 默认 defer 行为 |
动态创建 <script> | 不阻塞 DOMContentLoaded |
注意:
defer/async脚本仍会阻塞load事件(执行完成后才触发);defer脚本按顺序执行,async不保证顺序。
P-OPT11:React.memo 和 useMemo 的用法与区别
| 维度 | React.memo | useMemo |
|---|---|---|
| 类型 | 组件级 | Hook(值级) |
| 作用 | 浅比较 props,相同则跳过渲染 | 缓存计算结果 |
| 返回 | 记忆化组件 | 记忆化值 |
| 场景 | 子组件不需随父组件重渲染 | 复杂计算 / 引用稳定性 |
// React.memo
const Child = React.memo(function Child({ value }) {
console.log('Child render');
return <div>{value}</div>;
});
// useMemo
const sorted = useMemo(() => list.sort((a, b) => a.id - b.id), [list]);
// useCallback(缓存函数)
const onClick = useCallback(() => doSomething(id), [id]);注意:React.memo 浅比较对象 props 失效时需自定义比较函数(第二个参数)。
P-OPT12:页面加载白屏时间长的原因与优化
原因排查:
- HTML 体积过大:服务器响应慢;
- 关键 CSS / JS 阻塞渲染:未使用 defer / async;
- 第三方脚本拖累:广告 SDK、统计代码;
- 首屏 JS 体积大:未做代码分割;
- API 响应慢:数据返回后再渲染页面;
- 大量同步计算:主线程阻塞。
优化方案:
- 启用 CDN + gzip;
- 内联关键 CSS;
- 路由懒加载;
- SSR / 骨架屏;
- 异步数据预取;
- 移除未使用的第三方依赖。
P-OPT13:100000 条数据的列表如何展示?
核心:虚拟列表(Virtual List)
原理:只渲染视口内可见的列表项(约 10-30 个),滚动时动态替换 DOM。
主流库:
- React:
react-window、react-virtuoso、react-virtualized; - Vue:
vue-virtual-scroller; - 通用:自实现基于
IntersectionObserver。
简化实现思路:
function VirtualList({ items, itemHeight, height }) {
const [scrollTop, setScrollTop] = useState(0);
const startIdx = Math.floor(scrollTop / itemHeight);
const visibleCount = Math.ceil(height / itemHeight);
const visibleItems = items.slice(startIdx, startIdx + visibleCount);
const offsetY = startIdx * itemHeight;
return (
<div style={{ height, overflow: 'auto' }} onScroll={e => setScrollTop(e.target.scrollTop)}>
<div style={{ height: items.length * itemHeight, position: 'relative' }}>
<div style={{ transform: `translateY(${offsetY}px)` }}>
{visibleItems.map(item => <Row key={item.id} {...item} />)}
</div>
</div>
</div>
);
}进阶:动态高度、分片加载、滚动到顶部触发加载更多。
P-OPT14:DNS 预解析是什么?怎么实现?
作用:在用户点击链接前,浏览器提前解析域名到 IP,减少 DNS 查询延迟(50-200ms)。
<!-- 开启 DNS 预解析 -->
<link rel="dns-prefetch" href="//cdn.example.com">
<!-- 预连接(DNS + TCP + TLS,比预解析更彻底) -->
<link rel="preconnect" href="https://api.example.com">
<!-- 预加载(提前加载关键资源) -->
<link rel="preload" href="critical.css" as="style">
<!-- 预获取(下一页资源,优先级低) -->
<link rel="prefetch" href="/next-page.js">Chrome 默认开启:TLS 握手中的 hostname 也会被预解析。
P-OPT15:React 中可以做哪些性能优化?
详见 Q10,补充:
- 生产构建:生产环境 React 会移除 PropTypes / DevTools;
- 使用 Profiler:定位瓶颈组件;
- 不可变数据:避免
setState误判; - 键列表正确使用 key:避免 DOM 重建;
- React DevTools:可视化分析组件渲染次数;
- React.lazy + Suspense:按需加载;
- 避免 inline 对象 / 函数:每次渲染都是新引用,会破坏 memo。
P-OPT16:浏览器为什么限制并发数?
原因:
- 保护服务器:避免恶意页面同时打开上百个连接压垮源站;
- 保护客户端:每个 TCP 连接占用内存(接收/发送缓冲区)、端口;
- HTTP/1.1 队头阻塞:同域 6 个并发(Chrome / Firefox),其他浏览器 2-8 个不等;
- 历史包袱:RFC 2616 建议客户端不超过 2 个并发连接。
不同浏览器并发数(HTTP/1.1 同域):
| 浏览器 | 并发数 |
|---|---|
| Chrome | 6 |
| Firefox | 6 |
| Safari | 6 |
| IE 11 | 8 |
| IE 8-10 | 6-8 |
HTTP/2 解决:多路复用,单连接可承载无数请求。
P-OPT17:Performance API 是什么?怎么确定页面可用性时间?
Performance API:浏览器提供的性能监控接口。
关键时间点:
const t = performance.timing; // 已废弃,改用 PerformanceNavigationTiming
const nav = performance.getEntriesByType('navigation')[0];
const metrics = {
// DNS 查询
dns: nav.domainLookupEnd - nav.domainLookupStart,
// TCP 连接
tcp: nav.connectEnd - nav.connectStart,
// 首字节时间
ttfb: nav.responseStart - nav.requestStart,
// 响应下载
download: nav.responseEnd - nav.responseStart,
// DOM 解析
domParse: nav.domInteractive - nav.responseEnd,
// 资源加载
resourceLoad: nav.loadEventStart - nav.domContentLoadedEventEnd,
// 首屏可交互
fmp: nav.domContentLoadedEventEnd - nav.startTime
};可用性时间:domInteractive(DOM 可交互)或 domContentLoaded(DOM + 同步脚本完成)。
P-OPT18:window.requestAnimationFrame 是什么?
概念:浏览器在下一次重绘前调用指定回调函数,通常以 60fps 频率(16.7ms)。
function animate() {
el.style.transform = `translateX(${x++}px)`;
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);优势:
- 与显示器刷新率同步;
- 标签页隐藏时自动暂停,节省资源;
- 比
setTimeout(fn, 16)更精准;
典型场景:
- 动画 / 滚动联动;
- Canvas 绘制;
- 复杂 UI 渐进展示。
配套 API:
requestIdleCallback:浏览器空闲时调用,适合非关键任务;cancelAnimationFrame:取消回调。
P-OPT19:CSS 加载会造成阻塞吗?
会,但有条件:
| 加载方式 | 阻塞 |
|---|---|
<link rel="stylesheet"> | 阻塞渲染(但不阻塞 DOM 解析) |
内联 <style> | 阻塞渲染(取决于大小) |
media="print" / media="(max-width: 0)" | 不阻塞(媒体查询不匹配) |
rel="preload" as="style" | 不阻塞(需配合 onload 应用) |
阻塞原因:CSS 可能改变布局 / 样式,浏览器需等待 CSSOM 构建完成才能渲染。
优化:
<!-- 关键 CSS 内联 -->
<style>/* critical CSS */</style>
<!-- 非关键 CSS 异步加载 -->
<link rel="preload" href="main.css" as="style" onload="this.rel='stylesheet'">P-OPT20:什么是内存泄漏?什么原因导致?
定义:程序中不再使用的内存未被释放,长期累积导致应用卡顿甚至崩溃。
常见原因:
- 未清理的定时器 / 事件监听:
setInterval/addEventListener未在 unmount 时清理; - 闭包持有 DOM 引用:变量无意中保存了已移除 DOM;
- 意外的全局变量:未用 var/let/const 直接赋值;
- 游离的 DOM 引用:Map / WeakMap 中 key 是 DOM 节点,被移除后未清理;
- 循环引用:老 IE 问题,现代浏览器 GC 优化后较少;
- 未释放的 WebSocket / EventSource。
排查工具:
- Chrome DevTools Memory 面板;
performance.memory(Chrome 扩展 API);- 堆快照对比。
// 正确写法
onMounted(() => {
const timer = setInterval(tick, 1000);
const handler = () => console.log('resize');
window.addEventListener('resize', handler);
onBeforeUnmount(() => {
clearInterval(timer);
window.removeEventListener('resize', handler);
});
});P-OPT21:用 Webpack 优化前端性能
缩小打包体积:
- Tree Shaking(
mode: 'production'默认开启); - Scope Hoisting(
optimization.concatenateModules); - 按需引入(
babel-plugin-import); - 图片压缩(
image-webpack-loader); - Gzip / Brotli 压缩。
- Tree Shaking(
拆包策略:
splitChunks: { chunks: 'all', cacheGroups: { vendor: { test: /[\\/]node_modules[\\/]/, name: 'vendor' }, common: { minChunks: 2, name: 'common' } } }- 代码分割:
import()动态导入; - 持久化缓存:
cache: { type: 'filesystem' }; - 多线程:
thread-loader、terser-webpack-plugin(默认多核); - DLL / Module Federation:共享依赖;
- SourceMap 区分环境:开发用 eval-cheap-module,生产去掉;
- Bundle 分析:
webpack-bundle-analyzer。
P-OPT22:常规的前端性能优化手段
系统化总结(八层优化):
| 层级 | 手段 |
|---|---|
| 网络层 | CDN、HTTP/2、DNS 预解析、预连接、强缓存 |
| 资源层 | 压缩、合并、雪碧图、WebP、字体子集化 |
| 加载层 | 懒加载、预加载、代码分割、Tree Shaking |
| 渲染层 | SSR、SSG、骨架屏、避免重排重排 |
| 运行时 | Web Worker、防抖节流、虚拟列表 |
| 代码层 | 算法优化、避免全局查找、缓存计算结果 |
| 框架层 | React.memo、Vue v-once、组件细分 |
| 监控层 | Lighthouse、Web Vitals、APM |
原则:先测量、再优化、最后监控。
P-OPT23:什么是 CSS Sprites?
概念:将多个小图标合并为一张大图,通过 background-position 定位显示。
.icon { background: url('sprites.png') no-repeat; }
.icon-home { background-position: 0 0; width: 16px; height: 16px; }
.icon-user { background-position: -16px 0; width: 16px; height: 16px; }优点:
- 减少 HTTP 请求数(一次下载多图);
- 利于缓存(合并图一次缓存,多次复用)。
缺点:
- 维护成本高(新增图标需重新拼图);
- 不支持 Retina 高清屏(需提供 2x 图);
- HTTP/2 下意义不大(多路复用已减少请求成本)。
现代方案:
- SVG Sprite:多个 SVG 合并为一个
<symbol>,<use href="#icon-home" />引用; - 字体图标:Iconfont / Iconify;
- CDN 图标服务:按需加载 SVG。