← 全栈开发:从入门到入土
全栈开发全栈开发:从入门到入土

浏览器平台:技术栈里永远不会被废弃的那一层

从零到深入系列第 2 篇:Baseline、现代 CSS、ES2025 JavaScript、事件循环,以及进入严格模式与 Go 加速时代的 TypeScript。

HTMLCSSJavaScriptTypeScriptBaselineBrowser

框架是租来的,平台才是自己的。jQuery 教会了我们 DOM 操作,浏览器把它吸收成了 querySelector;Promise 库教会了我们异步,浏览器把它们吸收成了 async/await。每隔几年,框架最引以为傲的特性就会变成原生 API,然后框架悄悄删掉自己的代码。

这个进程从未像现在这样快。过去两年,浏览器发布了父选择器、container queries、原生嵌套、view transitions 和滚动驱动动画——这些功能曾让整整一代开发者以为永远离不开 JavaScript。与此同时,TypeScript 把严格模式变成了出厂默认,还用 Go 重写了编译器,提速 10 倍。第 2 篇讲的就是这块基石:2026 年中的平台能给你什么,以及在任何框架登场之前,如何直接构建在它之上。

为什么平台比任何框架都活得久

实用的论据是依赖的半衰期。一个基于 :has() 和 container queries 构建的设计系统十年后依然有效,因为浏览器几乎从不移除已发布的功能——Web 的向后兼容契约是整个软件行业最强的。而同一个系统如果构建在某个工具库或动画框架之上,就会继承那个库的发布节奏、破坏性变更和最终的弃坑。

还有一个恰好成立的招聘测试论据:当 AI Agent 写出组件的初稿时,审查的人需要知道浏览器本来就能做什么。否则你会合并进来 60 行 JavaScript,而它只是在重新实现一个 CSS 选择器。

Baseline:兼容性契约

“这个能用了吗?”过去意味着手动查四个浏览器版本。2026 年答案只有一个词:Baseline。一个特性在最新的 Chrome、Edge、Firefox 和 Safari 中都可用时,就是 Baseline Newly available;在四大浏览器中都稳定存在 30 个月后,变成 Widely available。MDN 和 Can I Use 都会内联展示这些徽章,所以任何 API 查询的最后都有一个明确的是或否。

工作规则:

  • Widely available——无条件使用。Grid、container queries、:has()、原生嵌套、Promise.withResolvers
  • Newly available——配合渐进增强发布:CSS 用 @supports 兜底,JS 用特性检测。滚动驱动动画和跨页面 view transitions 目前处于这一档(Firefox 的支持到 2026 年初才落地了一部分)。
  • 未达 Baseline——仅限实验。Temporal 是地平线上最重要的一个。

内容站点上有两个 Baseline 特性值得立刻采用:content-visibility: auto 让浏览器跳过屏幕外区域的渲染(web.dev 在一个长页面上测得渲染耗时从 232ms 降到 30ms),以及 <link rel="modulepreload">,它能消灭模块依赖瀑布。

HTML:语义就是 API

2026 年的 HTML 悄悄吸收了曾经需要库的组件。<dialog> 给你一个自带焦点圈定、::backdrop 样式和 Esc 关闭的模态框。popover 属性给你带 top-layer 渲染和 light-dismiss 的工具提示与菜单,不需要任何 portal 代码。<details> 开箱就是一个可访问的手风琴。表单校验——requiredpatternminlength:user-invalid——一行 JavaScript 都不用写就能跑。

真正重要的纪律没有变:按元素的本义使用元素。一个用于提交的 <button> 天然可以被键盘聚焦、被屏幕阅读器读出、被表单感知;一个绑了点击事件的 <div> 这些都做不到,加多少 ARIA 属性都补不齐差距。语义化 HTML 同时也是搜索引擎和 LLM 爬虫读得最可靠的页面版本——机器读者如今已经多于屏幕阅读器读者,而它们读的是同一份标记。

CSS 长大了

三个结构性特性改变了样式表的写法,今天它们都已经是 Baseline Widely available。

:has() 是我们等了十五年的父选择器。 它根据元素内部——或元素之后——的内容来决定样式。输入框聚焦时高亮所在的表单组、只在包含图片时改变布局的卡片、任一必填项为空时变暗的提交按钮:全部是纯 CSS,零事件监听。有团队反馈每个项目删掉了几百行切换 class 的 JavaScript。

Container queries 让组件与视口解耦。 在容器上设置 container-type: inline-size 之后,卡片响应的是它所在空间的宽度——侧边栏、模态框还是全宽列——而不是屏幕宽度。这直接杀死了 <Card layout="horizontal"> 这种用 variant prop 控制布局的模式。一个已发表的设计系统迁移案例把样式表从 84 KB 减到 52 KB,缩小 38%,大部分来自被删掉的 media query 覆盖。

原生嵌套和现代颜色补齐了与 Sass 的差距。 嵌套、自定义属性、color-mix()oklch() 意味着 2026 年的新项目日常开发不再需要 CSS 预处理器。本站就是一个例子:纯 CSS 加自定义属性——同一组变量用一份声明驱动两套配色主题。

动效与渲染:像素到底是怎么出现的

理解现代 CSS 动效为什么流畅,需要先理解渲染管线。浏览器把 HTML 和 CSS 解析成 DOM 和 CSSOM,合成渲染树,在 layout 阶段计算几何,在 paint 阶段栅格化,最后在 GPU 上 composite 合成图层。对 widthtop 做动画,每帧都要重跑 layout 和 paint;对 transformopacity 做动画,只触碰 composite 这一步。

在这条管线之上,两个 API 取代了整类整类的 JavaScript:

  • View transitions 为页面导航做动画——包括跨文档的 MPA 式跳转——并支持共享元素的连续过渡。列表页的缩略图可以形变成详情页的 hero 图。Chrome 和 Safari 从 2024 年起就已发布;Firefox 仍在补齐支持,所以把它当作渐进增强。
  • 滚动驱动动画 通过 animation-timeline: scroll()view() 把关键帧绑定到滚动位置,完全运行在合成器上。视差和滚动入场不再需要 IntersectionObserver、滚动监听器或 AOS 这类库。不可妥协的两条:用 @supports 兜底让不支持的浏览器直接显示内容;用 prefers-reduced-motion 规则照顾希望页面静止的用户。

由此可删掉的清单:用于嵌套和变量的 Sass、AOS/ScrollReveal、基础的 Popper.js 定位,以及 autoprefixer 的大部分工作。只把 GSAP 留给真正需要编排、手势驱动或基于物理的动效。

JavaScript 这门语言追上来了

ES2024 和 ES2025 补上了过去把人推向工具库的空缺,以下全部在 2026 年已达 Baseline:

  • Object.groupBy / Map.groupBy——分组集合不再需要 reduce 杂技
  • Promise.withResolvers()——创建一个可以从外部 settle 的 promise,不再需要别扭的构造函数包装
  • Iterator helpers——在迭代器和生成器上直接 .map().filter().take().drop(),惰性求值
  • Set 方法——unionintersectiondifferenceisSubsetOf,终于来了
  • Promise.tryRegExp.escapeArray.prototype.toSorted / toReversed / with(不可变更新),以及通过 import attributes 引入的 JSON 模块

Temporal——期待已久的 Date 替代品——在 2026 年中仍处于标准化的收尾阶段,而 TypeScript 6.0 已经内置了它的类型定义。等它落地再学;在那之前,Intl.DateTimeFormat 足以诚实地处理本地化格式化。

元教训:添加依赖之前,先搜一下 MDN。2026 年,答案越来越多地是“平台本身就能做”。

事件循环依然是面试题

每个 JavaScript 运行时都跑同一个循环:取一个任务(setTimeout 回调、I/O 事件、DOM 事件),在调用栈上把它跑完,然后彻底清空微任务队列——promise 回调、queueMicrotask、MutationObserver——之后才可能会渲染一帧,再取下一个任务。

在生产环境中真正会咬人的两个推论:

  • Promise.resolve().then(fn) 永远先于 setTimeout(fn, 0) 执行——微任务在当前任务之后清空,而定时器要等下一个任务。
  • 一个无限调度自身的微任务会彻底饿死渲染;递归的 setTimeout 不会,因为任务之间有机会渲染。这就是“页面冻死”和“页面只是慢”的区别。

框架调度器、React 的并发渲染,以及你会写的每一个 loading 转圈,都是在与这个循环谈判。

TypeScript:默认严格,Go 来加速

TypeScript 在 2026 年的故事是两个版本。TypeScript 6.0(2026 年 3 月)是最后一个基于 JavaScript 的编译器——也是一次刻意的大扫除:strict: true 成为默认值,ESM 成为默认模块系统,ES5 目标和 moduleResolution: "classic" 被移除,ES2025 库类型内置。严格不再是你需要争取的团队规范,而是你需要说服别人不要关掉的出厂设置。

TypeScript 7.0(RC,2026 年 6 月)是同一个编译器的 Go 移植版——是移植而不是重写,所以类型检查语义完全一致,但微软在 VS Code 那 150 万行代码上的基准从 77.8 秒降到 7.5 秒,并默认启用四个并行 checker worker。稳定版预计在本文写作后数周内发布;消费编程式 API 的工具(typescript-eslint、ts-morph)应该等 7.1。还有一个悄悄重要的变化:Node 24 可以通过剥离可擦除类型直接执行 TypeScript 文件,这让脚本和小工具变成了零构建的事。

TypeScript 不做什么同样重要:类型在编译时被擦除,永远不会到达运行时。任何跨越边界的东西——API 响应、表单输入、环境变量——仍然需要运行时校验。这个边界正是第 6 篇会重新拾起的问题。

练习,然后进入第 3 篇

在继续之前,不借助任何框架,向自己证明平台的能力:

  1. 写一个语义化 HTML 页面——header、nav、main、article、footer——用原生嵌套和自定义属性做样式,再加一个由 container query 驱动的卡片组件,要求它在宽列和窄侧边栏里都正常工作。
  2. 加一个表单,校验状态(聚焦、非法、可提交)完全用 :has():user-invalid 做样式——不用 JavaScript。
  3. 先预测,再在 DevTools 里验证:把 setTimeoutPromise.thenqueueMicrotask 混在一个脚本里,日志的输出顺序是什么。
  4. typescript@latest 初始化一个项目,确认 strict 默认开启,并感受一下 7.0 RC 上 tsc --noEmit 有多快。

第 3 篇会在这一切之上搭建 React 19——而你会认出每一个零件:组件只是函数,Server Components 只是搬到服务端的渲染管线,Actions 则是平台原生表单的升级版。

guest@swangnice:~$