如果你已经会用 Vue / React,却常纠结「要不要上最新 CSS 特性」「性能到底该怎么取舍」,这篇踩坑故事 + 代码片段合集,也许能帮你少走 10 个弯路。
引言
最近在整理电脑里的老代码,翻到了几年前写的那些“杰作”,简直不忍直视。想起刚入行时,觉得会个 jQuery 就天下无敌,现在想想真是 too naive。
趁着周末有空,把这些年折腾过的技术重新串成 4 个主题、10 个片段,算是对自己的总结,也希望对你有用。
一、布局演进:媒体查询 → 容器查询
1.1 场景再现
两个月前做管理后台,产品经理拿着原型图说:
“这个卡片组件要能适配各种宽度的容器。”
我第一反应:简单,媒体查询一把梭。
/* before:只能根据屏幕宽度做判断 */
@media (max-width: 600px) {
.card { flex-direction: column; }
}
结果卡片在主内容区正常,塞进侧边栏直接变形——媒体查询只看屏幕,不看容器。
1.2 容器查询真香
/* 1. 先声明容器 */
.container {
container-type: inline-size;
}
/* 2. 再写容器级别的查询 */
@container (min-width: 400px) {
.card {
display: flex;
flex-direction: row;
}
.card-image {
width: 100px;
flex-shrink: 0;
}
}
Key Takeaways
- 兼容:Chrome 105+、Safari 16+、Edge 105+,Firefox 仍 behind flag。
- 进阶:
@container style(--theme: dark)还能响应自定义属性,玩出花。
二、性能优化:从“乱卷”到“场景化”
2.1 瞎卷的黑历史
曾把一个简单列表搞成虚拟滚动 + 预加载 + 缓存,性能是快了,维护成本爆炸。新同事灵魂拷问:
“这么复杂有必要吗?”
2.2 精准打击一例:content-visibility
.article-content {
content-visibility: auto;
contain-intrinsic-size: 1000px; /* 估算高度,避免抖动 */
}
实测同篇文章首屏渲染 1 s → 300 ms(Lighthouse 6 次平均)。
⚠️ 风险:估算不准会导致滚动条抽搐,需用 contain-intrinsic-size: auto 1000px 动态校正。
Key Takeaways
- 优化前先问三个问题:瓶颈在哪?数据量级?ROI 如何?
- 90% 的性能问题靠“少做”而非“多做”。
三、异步与状态:从回调地狱到极简 Store
3.1 回调 → Promise → async/await
// before:回调地狱
getData(r1 => processData(r1, r2 => saveData(r2, () => {})));
// after:async/await
const r1 = await getData();
const r2 = await processData(r1);
await saveData(r2);
3.2 10 万行 Excel 不卡死:Web Workers
主线程:
const worker = new Worker('data-worker.js');
worker.postMessage({ data: excelData, action: 'process' });
worker.onmessage = e => {
e.data.type === 'progress' && updateProgressBar(e.data.percent);
e.data.type === 'complete' && renderChart(e.data.result);
};
data-worker.js:
self.onmessage = ({ data }) => {
const { data: rows } = data;
const result = rows.map((row, i) => {
if (i % 1000 === 0) {
self.postMessage({ type: 'progress', percent: Math.floor(i / rows.length * 100) });
}
return heavyCompute(row);
});
self.postMessage({ type: 'complete', result });
};
小技巧:大数组 postMessage 时加 transfer: [buffer] 可再省 30% 拷贝时间。
3.3 状态管理:30 行够用版
function createStore(initialState) {
let state = initialState;
const listeners = new Set();
return {
getState: () => state,
setState: patch => {
state = { ...state, ...patch };
listeners.forEach(l => l(state));
},
subscribe: cb => (listeners.add(cb), () => listeners.delete(cb))
};
}
如需异步,可在 setState 前包一层 Promise,或直接上 Pinia / Zustand。
Key Takeaways
- 小项目:手写 store 足够,别急着上 Redux Toolkit。
- 大项目:类型安全、中间件、DevTools 依旧首推 Redux / Pinia。
四、工程踩坑:依赖、兼容、移动端
4.1 依赖升级 3 步曲
- 读 changelog(尤其是 minor / patch 的 BREAKING 部分)。
- 切分支 + CI 跑全量测试。
- 核心依赖锁版本,非核心用 Renovate 自动提 MR。
4.2 Safari 日期格式化坑
// Safari Invalid Date
new Date('2023-12-25');
// 通用写法
new Date('2023/12/25');
4.3 iOS 输入框缩放
input, textarea {
font-size: 16px; /* 小于 16px 会触发缩放 */
}
4.4 一键调试 CSS 轮廓
[data-debug] * {
outline: 1px solid #f00;
outline-offset: -1px;
}
书签脚本(拖到收藏栏即可):
javascript: (() => {
document.documentElement.toggleAttribute('data-debug');
})();
五、10 个即插即用的片段
| # | 场景 | 代码 |
|---|---|---|
| 1 | 监控函数耗时 | const timed = (fn, name) => (...a) => { const s = Date.now(); const r = fn(...a); console.log(${name} took ${Date.now()-s}ms); return r; }; |
| 2 | 智能加载图片 | `const loadImg = (img, src) => img.src = /2g/.test(navigator.connection?.effectiveType) ? src.replace(/.(jpg |
| 3 | 防抖 | const debounce = (f, d) => { let t; return (...a) => (clearTimeout(t), t = setTimeout(() => f(...a), d)); }; |
| 4 | 节流 | const throttle = (f, w) => { let i; return (...a) => { if (!i) (f(...a), i = 1, setTimeout(() => i = 0, w)); }; }; |
| 5 | 16 进制随机色 | const randomColor = () => '#' + Math.random().toString(16).slice(2, 8).padEnd(6, '0'); |
| 6 | 复制到剪贴板 | const copy = txt => navigator.clipboard?.writeText(txt); |
| 7 | 暗黑模式监听 | window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => console.log(e.matches)); |
| 8 | 检测是否移动端 | `const isMobile = /iPhone |
| 9 | 元素是否在视口 | const inViewport = el => el.getBoundingClientRect().top < window.innerHeight; |
| 10 | 轻量级事件总线 | const bus = new (class extends EventTarget { emit(k, d) { this.dispatchEvent(new CustomEvent(k, { detail: d })); } on(k, cb) { this.addEventListener(k, e => cb(e.detail)); } }); |
六、技术选型 3 问
- 团队熟悉度 > 技术先进性
- 项目规模 决定架构深度
- 维护周期 决定抽象层级
最近一个小项目本想用 React,但考虑到后续由 2 名后端同事维护,最终选了 Vue3 +
<script setup>,学习成本低、文档友好。
写在最后
回顾这几年的前端路,技术更新确实很快,但核心的东西变化不大。HTML、CSS、JavaScript的基础还是最重要的。
新技术要学,但不要盲目跟风。很多时候,用好现有的技术比追新更有意义。
还有就是多写代码,多踩坑。看再多文章也不如亲自动手写一遍。每个Bug都是成长的机会,每次重构都是能力的提升。
前端这条路没有尽头,但正是这种不确定性让它有趣。新的一年,继续学习,继续折腾!
写这篇文章的时候翻了好多老代码,有些写得真的很烂,但也记录了当时的思考。技术在进步,我们也在成长。如果你也有类似的经历或者想法,欢迎留言交流!
2004

被折叠的 条评论
为什么被折叠?



