从回调地狱到容器查询:一个前端老兵的 6 个技术转折与 10 个实用片段

如果你已经会用 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 步曲

  1. 读 changelog(尤其是 minor / patch 的 BREAKING 部分)。
  2. 切分支 + CI 跑全量测试。
  3. 核心依赖锁版本,非核心用 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)); }; };
516 进制随机色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 问

  1. 团队熟悉度 > 技术先进性
  2. 项目规模 决定架构深度
  3. 维护周期 决定抽象层级

最近一个小项目本想用 React,但考虑到后续由 2 名后端同事维护,最终选了 Vue3 + <script setup>,学习成本低、文档友好。


写在最后

回顾这几年的前端路,技术更新确实很快,但核心的东西变化不大。HTML、CSS、JavaScript的基础还是最重要的。

新技术要学,但不要盲目跟风。很多时候,用好现有的技术比追新更有意义。

还有就是多写代码,多踩坑。看再多文章也不如亲自动手写一遍。每个Bug都是成长的机会,每次重构都是能力的提升。

前端这条路没有尽头,但正是这种不确定性让它有趣。新的一年,继续学习,继续折腾!


写这篇文章的时候翻了好多老代码,有些写得真的很烂,但也记录了当时的思考。技术在进步,我们也在成长。如果你也有类似的经历或者想法,欢迎留言交流!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值