前端开发工具全景

涵盖语言、框架、构建工具、运行时、生态库的完整前端开发图谱 · 来源: 各项目官方文档 · GitHub

1. 前端开发生态全景图

前端开发涉及多层技术栈,从底层语言到上层框架、从开发工具到生产部署,110+ 工具/技术、24 大分类覆盖。图 1-1 以分类地图形式(按用途归类、无依赖连线)呈现生态全貌;后三张全景图分别从开发管线、技术分层、浏览器运行时给出流程与架构视角。除语言 / CSS / 动效 / 构建 / 框架 / 组件库 / 路由 / 状态 / 元框架 / 测试 / 代码质量 / 开发工具 / 运行时等经典分类外,还覆盖数据获取、数据校验、表单、组件文档、微前端、Monorepo、跨端桌面、Git 工作流、Mock、性能调试等第二梯队热门分类。

图 1-1: 前端开发技术生态全景(分类地图 · 2026)

PlantUML diagram

1.1 生态补充分类 — 各分类流行工具速查

图 1-1 在原 14 大分类基础上,补充了 10 个第二梯队热门分类(含扩充现有分类的成员)。下表按分类列出补充工具的定位与适用场景。

分类工具是什么 / 解决什么问题适用场景
数据获取 / 服务端状态 TanStack Query · SWR · Apollo 把「服务端数据」做成可缓存的客户端状态:自动缓存/失效/重试/乐观更新,替代手写 useEffect + fetch。 列表/详情需缓存与并发去重、轮询、无限滚动;GraphQL 项目用 Apollo。
数据校验 Zod · Valibot · Yup 运行时数据校验与 schema 定义,可与 TypeScript 类型联动(Zod 直接推导类型),表单提交前统一校验。 表单验证、API 响应/环境变量校验、前后端共享 schema。
表单 React Hook Form · Formik · VeeValidate 把「受控组件 + 手写 state」替换为表单状态管理:校验、提交、错误提示、字段联动。 复杂表单(多字段、条件字段、动态增删);React 用 RHF/Formik,Vue 用 VeeValidate。
文档 & 组件文档 Storybook · VitePress · Docusaurus Storybook 为组件库做可视化开发/文档/测试;VitePress 面向 Vue/Vite 快速建站;Docusaurus 面向 React 文档站。 团队组件库、开源库、技术博客与 API 文档站。
微前端 qiankun · Module Federation · wujie 把一个巨型应用拆成多个独立部署/独立技术栈的子应用,运行时组合成整体。 多团队并行、老系统渐进改造、中后台平台化。
Monorepo Turborepo · Nx 多包仓库的编排工具:任务缓存、增量构建、依赖图、统一命令与版本管理。 多包/多应用同仓管理、共享代码库(如 pnpm workspace + Turborepo)。
跨端 / 桌面 Electron · Tauri · Capacitor 用 Web 技术做桌面应用(Electron 重、Tauri 轻)或移动端 App 壳(Capacitor 包 Web 到原生)。 桌面工具/客户端(VS Code、Notion 用 Electron);跨端 App。
Git 工作流 husky · commitlint · Changesets · semantic-release 提交前钩子跑 lint/test(husky)、规范 commit 信息(commitlint)、版本号与 changelog 自动化(Changesets/semantic-release)。 团队 commit 规范、开源包版本发布、自动化发布流水线。
Mock / 数据模拟 MSW · json-server 拦截网络请求返回模拟数据:MSW 在 Service Worker 层拦截(前后端同契约)、json-server 起一个假 REST API。 后端未就绪时前端先行开发、E2E 测试稳定化。
性能 & 调试 Lighthouse · WebPageTest 性能/SEO/无障碍审计:Lighthouse 内置于 Chrome DevTools;WebPageTest 多地域多设备实测。 上线前性能体检、Core Web Vitals 达标、竞品对比。
CSS 生态补充 UnoCSS · Emotion · vanilla-extract · Panda CSS UnoCSS 为即时原子化引擎(Tailwind 的更快替代);Emotion 为 CSS-in-JS;vanilla-extract 编译期 CSS 模块;Panda CSS 类型安全原子类。 按团队偏好选型;追求性能/类型安全/可维护性时替换 Tailwind 或 styled-components。
状态管理补充 Vuex · Recoil · Valtio Vuex 为 Vue2 时代官方状态库(新项目用 Pinia);Recoil 为 React 原子状态;Valtio 基于 Proxy 的响应式状态。 维护存量 Vue2 项目(Vuex);React 细粒度状态(Recoil/Valtio)。
选型提示:以上「第二梯队」并非不重要——数据获取(TanStack Query)与数据校验(Zod)在现代工程中几乎是标配;文档(Storybook)、微前端、Monorepo 则按团队规模按需引入。判断是否引入的标准是:该问题是否高频出现、是否值得用约定换收益。

2. 开发管线全景

从编码到上线的完整前端开发管线:编码 → lint/格式化 → 版本控制 → CI/CD → 构建 → 部署 → 浏览器渲染。

图 2-1: 前端开发管线全景 — 从编码到上线

PlantUML diagram
术语解释:

3. 技术分层全景

前端技术栈的六层架构:运行平台 → 语言/类型系统 → 构建工具 → 前端框架 & 状态管理 → 组件库 → 元框架 & 部署。补充说明新分类的归属:数据获取(TanStack Query/SWR/Apollo)、数据校验(Zod/Valibot/Yup)属于第 4 层的应用逻辑层;表单方案(React Hook Form/Formik/VeeValidate)属于第 5 层的 UI 方案层;Monorepo(Turborepo/Nx)属于第 3 层的工程化工具;微前端(qiankun/Module Federation)与桌面跨端(Electron/Tauri/Capacitor)属于第 6 层的交付形态。注意区分:前端框架(Vue/React)提供组件模型与响应式渲染,本身不带现成 UI;组件库(Element Plus/Ant Design)构建在框架之上,交付带样式、带行为的现成组件。

图 3-1: 前端技术分层全景 — 六层架构

PlantUML diagram
补充辨析:「前端颜值」由谁决定?

组件库只是上层"成品",不是颜值上限。真正决定观感的是三层协作:

层级代表决定什么权重
设计规范色板 / 栅格 / 间距 / 圆角 / 字体层级审美品位、视觉上限(源头)最高
CSS 方案Tailwind / CSS Modules / CSS-in-JS样式如何组织、能否精细微调高
组件库 / UI 库Element Plus / Ant Design / shadcn/ui默认好看的下限(及格线),提效中
前端框架Vue / React / Svelte几乎不管颜值——只管逻辑与结构低

一句话:组件库决定"最差能有多好看",CSS 方案决定"能不能精细掌控",设计规范决定"上限"。两个用同款 Ant Design 的团队,一个能做得精致、一个可以同样难看——差距在布局、色彩运用、内容密度与动效,这些组件库管不了。

案例演示:同款 Ant Design,颜值由谁决定?

三组演示都用同一份「卡片 + 按钮」组件代码,只改外层 CSS,直观看出每一层对观感的真实影响。

① 组件库只决定「下限」——直接堆叠 vs 设计规范精修

直接堆叠 · 及格线

项目概览

今日访问 128,订单 32,转化 25%。

本周新增用户 45,较上周 +12%。

设计规范精修 · 上限

项目概览

今日访问128+8.2%
订单32+12.5%
转化率25%-1.0%
同款库、同款组件——左边是默认样式直接堆(无间距、无层级、红蓝乱撞),右边只用「间距规范 + 色彩层级 + 主次按钮 + 圆角阴影」就完全变了一个产品。差距全部来自设计规范与 CSS 方案,组件库给不了也管不了。
<!-- ================= ① 同一份「卡片 + 按钮」组件代码 ================= -->
<!-- 组件库(Ant Design)只提供结构:div.card + button.btn,好看与否它不管 -->

<div class="antd-card">
  <h3>项目概览</h3>
  <p>今日访问 128,订单 32,转化 25%。</p>
  <p>本周新增用户 45,较上周 +12%。</p>
  <button class="antd-btn">查看详情</button>
  <button class="antd-btn antd-btn-danger">导出报表</button>
</div>

<!-- ====== 左:直接堆叠(及格线)—— 组件库自带默认样式,仅此而已 ====== -->
.antd-card { padding: 0; }        /* 无间距规范:文字贴边 */
h3 { margin: 0; }                 /* 无字体层级:标题与正文同样大 */
.antd-btn { background: #fff; }   /* 无色彩规范:灰底白字直接怼 */
.antd-btn-danger { color: red; }  /* 色彩乱用:红字突兀刺眼 */

<!-- ====== 右:设计规范精修(上限)—— 用间距/色板/层级覆盖组件库默认 ====== -->
.antd-card {
  padding: 16px 20px;            /* 间距规范:统一 8px 栅格 → 呼吸感 */
  border-radius: 12px;           /* 圆角规范:与全站一致 */
  box-shadow: 0 4px 16px rgba(100,108,255,.12);  /* 轻投影提质感 */
}
.antd-card h3 { font-size: 16px; font-weight: 600; color: #1a1a2e; }  /* 字体层级 */
.antd-card p  { font-size: 13px; color: #555; }                        /* 次级信息降权 */
.antd-btn { background: linear-gradient(135deg,#646cff,#bd34fe); color:#fff; border:none; }
.antd-btn-danger { color: #d1242f; }   /* 危险色仅用于「危险操作」,不滥用 */

② CSS 方案决定「能不能精细掌控」——同样的设计稿,能否逐像素还原

只有默认 CSS · 做不到

响应式导航

  • 首页
  • 产品
  • 定价
  • 文档
窄屏挤成一团、hover 无反馈、间距无法按设计稿微调
Tailwind / CSS Modules · 精确掌控

响应式导航

  • 首页
  • 产品
  • 定价
  • 文档
flex + gap 对齐、hover 高亮、断点自适应、间距逐像素可控
同一份设计稿,CSS 方案决定「能不能精确还原」:Tailwind 一行 md:flex gap-4 就能做到的响应式栅格,纯手写 CSS 也能做,但成本高、一致性差。CSS 方案决定精细掌控力。
<!-- ============ ② 同样的设计稿:导航栏「hover 高亮 + 自适应」 ============ -->

<!-- 方案 A:纯手写 CSS —— 能做,但要自己管间距、断点、状态,且易写乱 -->
ul.nav { display: flex; }
ul.nav li { padding: 8px; }
@media (max-width: 600px) { ul.nav { flex-direction: column; } }  /* 断点要自己写 */
ul.nav li:hover { background: #646cff; color: #fff; }

<!-- 方案 B:Tailwind —— 一条 class 全搞定,间距/断点/状态都是原子化的 -->
<!-- 对应上面的渲染效果:窄屏自动竖排、hover 高亮、间距 6px 精确可控 -->
<ul class="flex gap-1.5 md:flex-col">
  <li class="flex-1 rounded-lg text-center py-2 bg-indigo-100 text-indigo-600
             transition hover:bg-indigo-600 hover:text-white">首页</li>
  ...
</ul>
<!-- 同样用 CSS 方案(Tailwind / CSS Modules / CSS-in-JS),
     差别只在「组织方式」:能否用变量统一间距、能否原子化复用、能否断点化 ——
     这就是「高权重」的含义:方案决定你精细化调优的效率天花板。 -->

③ 前端框架「不管颜值」——换框架,观感零变化

Vue 写的按钮
React 写的按钮
原生 JS 写的按钮
三个按钮分别由 Vue / React / 原生 JS 渲染,不写一行样式——长得一模一样(浏览器默认外观)。框架只负责把组件渲染出来,颜值完全由 CSS 层决定;写不写样式、写得好不好,框架既不参与也不干预。
<!-- ============ ③ 三种框架渲染同一个按钮,都不写 CSS ============ -->

<!-- Vue 3 写法 -->
<template><button>未加样式</button></template>

<!-- React 写法 -->
function Btn() { return <button>未加样式</button>; }

<!-- 原生 JS 写法 -->
document.body.innerHTML = '<button>未加样式</button>';

<!-- 结果:三者输出完全相同的原生 <button> 元素,浏览器统一套用默认外观。
     框架只负责「渲染逻辑」,像素观感全归 CSS。要让按钮变好看,
     得给 <button> 加 CSS(class/内联/样式表)—— 框架帮不上忙。 -->

4. 浏览器核心架构

浏览器是前端代码的终极运行环境。理解渲染引擎(Blink/WebKit/Gecko)、JS 引擎(V8/SpiderMonkey)、Event Loop 和 Web APIs 是深入前端开发的基础。

图 4-1: 浏览器核心架构 — 前端运行环境

PlantUML diagram

HTML / CSS / JavaScript / TypeScript 前端核心技术体系

综合梳理:MDN Web Docs · WHATWG HTML Living Standard · W3C CSS WG · TC39 ECMA-262 · TypeScript

1. 概述 — 网页三层架构

一个网页由三门互补的语言构成,各司其职、分层解耦:

技术职责类比语言类型标准组织
HTML
(HyperText Markup Language)
结构:定义内容和语义(标题、段落、图片、链接)房子的骨架标记语言WHATWG(Living Standard,无版本号)
CSS
(Cascading Style Sheets)
表现:定义外观(颜色、字体、布局、动画)装修和外观样式语言W3C CSS WG(模块化,各模块独立分级演进)
JavaScript行为:定义交互逻辑(响应点击、请求数据、修改页面)水电和电器编程语言(唯一图灵完备)Ecma TC39(ECMA-262,2015 起每年 6 月发一版)
三者的关系
HTML 是宿主:CSS 通过 <link>/<style> 挂到 HTML 上,JS 通过 <script> 挂到 HTML 上。 浏览器把 HTML 解析成 DOM 树、把 CSS 解析成 CSSOM,JS 则通过 DOM/CSSOM API 在运行时读写这两棵树——这就是"动态网页"的本质。
标准现状(要点)
① "HTML5" 只是历史名称,2019 年后唯一标准是 WHATWG 维护的 HTML Living Standard,持续滚动更新;
② CSS 没有"CSS4",CSS3 之后拆成独立模块(Selectors Level 4、Grid Level 2 等各自演进);
③ JavaScript 的标准名是 ECMAScript(ES2015/ES6 是分水岭,此后每年一版:ES2016 … ES2025+)。

第一部分:行为图(浏览器如何运作这三者)

2.1 时序图 — 页面加载全流程

从输入 URL 到页面可交互,三门语言依次登场:HTML 先到并开始解析,遇到 CSS 和 JS 再分别请求。注意两个关键阻塞点:同步 <script> 阻塞 HTML 解析;CSSOM 未就绪会阻塞 JS 执行(因为 JS 可能读样式)。

图 2.1-1: 页面加载时序图

PlantUML diagram

专业术语

DOM (Document Object Model)
HTML 解析后生成的内存树结构,JS 通过它读写页面节点。浏览器解析 HTML 时逐步构建,遇同步 script 时暂停。
CSSOM (CSS Object Model)
CSS 解析后生成的对象模型,与 DOM 类似。CSSOM 未就绪时会阻塞 JS 执行(因为 JS 可能读取计算样式)。
阻塞解析 (Parser Blocking)
同步 <script> 标签在下载和执行期间会暂停 HTML 解析,是页面加载性能的关键瓶颈。
defer / async / module
三种异步加载策略:defer 并行下载、HTML 解析完后按顺序执行;async 下载完即执行、顺序不定;module 默认行为等同于 defer。
DOMContentLoaded
在 HTML 解析完成、所有 defer 脚本执行完毕后触发,DOM 树已完整。比 load 事件早很多(load 需等所有图片等资源)。
load 事件
页面所有资源(图片、CSS、JS、iframe 等)全部加载完毕后才触发,是最晚的页面事件。

案例演示:同步脚本为什么阻塞

<!DOCTYPE html>
<html>
<body>
  <h1>页面加载时序演示</h1>

  <!-- ① 同步脚本:HTML 解析暂停,等它下载 + 执行 -->
  <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>

  <p>这段 p 要等 lodash 加载执行完才能被解析到</p>

  <!-- ② defer:并行下载,HTML 解析完后按序执行 -->
  <script src="analytics.js" defer></script>

  <!-- ③ async:下载完立即执行,不保证顺序 -->
  <script src="widget.js" async></script>

  <p>此 p 不受 defer/async 影响,正常解析</p>
</body>
</html>
输出时序:浏览器 DevTools → Network 面板可见:同步脚本(白色条=下载,黄色条=执行)插入在 HTML 解析中间;defer/async 的下载与 HTML 解析(蓝色条)并行。
// 测量关键时间点
window.addEventListener('DOMContentLoaded', () => {
  console.log('DOM 树就绪(defer 脚本已执行)—', performance.now());
});
window.addEventListener('load', () => {
  console.log('全部资源加载完毕 —', performance.now());
});
// DOMContentLoaded 通常远早于 load
脚本加载方式下载时机执行时机是否阻塞解析
<script>(同步)解析到标签时下载完立即执行阻塞(下载+执行期间暂停解析)
<script defer>并行下载HTML 解析完、DOMContentLoaded 前,按顺序不阻塞
<script async>并行下载下载完立即执行,顺序不定执行时阻塞
<script type="module">并行下载(含依赖图)默认 defer 行为不阻塞

2.2 活动图 — 浏览器渲染流水线

浏览器把 HTML + CSS 变成屏幕像素的固定流水线:DOM → CSSOM → Render Tree → Layout → Paint → Composite。JS 修改页面后,会触发流水线的部分重跑——改几何属性触发 回流 Reflow(代价大),只改颜色等触发 重绘 Repaint,只改 transform/opacity 则仅需 合成 Composite(最便宜,动画首选)。

图 2.2-1: 渲染流水线活动图

PlantUML diagram

专业术语

Render Tree (渲染树)
DOM 树与 CSSOM 合并后生成的树,只包含可见节点(排除 display:none、<head> 等),是 Layout 的输入。
Layout / Reflow (布局 / 回流)
计算每个可见元素的确切位置和尺寸。改 width、height、left、font-size 等几何属性会触发回流,代价最大。
Paint / Repaint (绘制 / 重绘)
将 Layout 结果转换为像素填充指令(颜色、阴影、文字等)。改 color、background 等非几何属性仅触发重绘。
Composite (合成)
GPU 将多个层合并上屏的最终步骤。只改 transform 和 opacity 时跳过后面的 Layout/Paint,仅触发合成,动画首选(60fps 无压力)。
GPU 加速
将元素提升为独立的合成层(如 will-change: transform),使其动画在 GPU 上完成而不占用主线程。

案例演示:三种操作触发不同代价的渲染

// 准备:获取一个 div
const box = document.querySelector('.box');

// 【回流 Reflow】— 改了几何,代价最大
box.style.width = '300px';   // Layout → Paint → Composite
box.style.height = '200px';
box.style.left = '100px';
box.style.fontSize = '20px';

// 【重绘 Repaint】— 只改颜色,跳过 Layout
box.style.color = 'red';
box.style.background = '#eee';
box.style.boxShadow = '0 2px 8px rgba(0,0,0,0.3)';

// 【仅合成】— transform/opacity,GPU 处理,60fps 无压力
box.style.transform = 'translateX(100px) rotate(15deg)';
box.style.opacity = '0.8';
DevTools 验证:打开 Performance 面板录制 → 红线(Layout)= 回流、绿线(Paint)= 重绘、无红绿线 = 仅合成。
最佳实践:动画只用 transform + opacity,并加 will-change: transform 提升为合成层。
// 坏 vs 好:动画对比
// 坏 — 每帧触发回流,卡顿
el.style.top = y + 'px';  el.style.left = x + 'px';

// 好 — 仅合成,丝滑 60fps
el.style.transform = `translate(${x}px, ${y}px)`;

2.3 活动图 — JS 事件循环 (Event Loop)

JS 是单线程语言,靠事件循环实现异步:调用栈清空后,先清空微任务队列(Promise.then、queueMicrotask),再看是否需要渲染,然后取一个宏任务(setTimeout、I/O、点击回调)循环往复。这是理解 async/await、页面卡顿、任务优先级的钥匙。

图 2.3-1: 事件循环活动图

PlantUML diagram

专业术语

Call Stack (调用栈)
JS 引擎执行同步代码的后进先出栈。栈空时才从任务队列取任务执行。栈溢出(Stack Overflow)即调用栈过深。
Macro Task (宏任务 / Task)
由宿主环境(浏览器/Node)发起的任务,如 setTimeout、setInterval、I/O、用户事件回调。每轮事件循环从宏任务队列取一个执行。
Micro Task (微任务)
由 Promise 或 queueMicrotask 产生的任务。每个宏任务执行完后、渲染之前,必须清空微任务队列。执行中新增的微任务也会在本轮被清空,可能饿死渲染。
requestAnimationFrame (rAF)
浏览器在每帧渲染前调用的回调,执行时机在微任务之后、Layout 之前。适合做帧同步的动画逻辑,与屏幕刷新率(通常 60Hz ≈ 16.7ms/帧)对齐。
渲染时机
浏览器并非每轮事件循环都渲染,而是以约 16.7ms(60fps)为帧间隔。渲染发生在微任务清空之后、下一个宏任务之前。

案例演示:彻底搞懂宏/微任务顺序

// 打开控制台运行这段,观察输出顺序
console.log('== 开始 ==');

// 宏任务 1: script 本身(当前正在执行的就是第一个宏任务)

setTimeout(() => {
  console.log('A setTimeout 0 — 宏任务');
  Promise.resolve().then(() => console.log('B Promise 在 setTimeout 里 — 微任务'));
}, 0);

setTimeout(() => console.log('C setTimeout 0 第二个 — 下一个宏任务'), 0);

Promise.resolve().then(() => console.log('D Promise 1 — 当前宏任务的微任务'));
Promise.resolve().then(() => {
  console.log('E Promise 2 — 当前宏任务的微任务');
  queueMicrotask(() => console.log('F queueMicrotask 套娃 — 本微任务清空期内执行'));
});

requestAnimationFrame(() => console.log('G rAF — 渲染前回调'));
console.log('== 同步结束 ==');
输出顺序:开始 → 同步结束 → D → E → F → A → B → C → G(rAF 下一帧)
规律:① 当前宏任务全部同步代码 → ② 微任务清空(含套娃) → ③ rAF 回调 → ④ 渲染 → ⑤ 下一个宏任务
// 经典面试题
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');
// 输出: 1 4 3 2 —— 微任务永远插队在下一个宏任务之前

2.4 时序图 — DOM 事件流(捕获 → 目标 → 冒泡)

用户点击一个按钮时,事件并不是直接派发给按钮:先从 window 自上而下捕获到目标,再自下而上冒泡回去。"事件委托"(把监听器挂在父元素上统一处理子元素事件)正是利用冒泡阶段。

图 2.4-1: 事件捕获与冒泡时序图

PlantUML diagram

专业术语

Capture Phase (捕获阶段)
事件从 window 开始,沿 DOM 树自上而下传播到目标元素的阶段。addEventListener(type, fn, true) 的第三个参数 true 在此阶段触发。
Target Phase (目标阶段)
事件到达目标元素本身,触发绑定在目标上的监听器,不分捕获或冒泡。
Bubble Phase (冒泡阶段)
事件从目标元素自下而上传播回 window。这是 addEventListener 的默认触发阶段,也是事件委托依赖的阶段。
Event Delegation (事件委托)
将监听器挂在父元素上,利用事件冒泡统一处理所有子元素的事件。用 e.target 判断实际触发者,避免为每个子元素单独绑定,性能好且支持动态添加的元素。
stopPropagation
e.stopPropagation() 阻止事件继续在 DOM 树中传播(不影响同一元素上的其他监听器);e.stopImmediatePropagation() 更彻底,连同一元素的其他监听器也阻止。

案例演示:事件委托 & 捕获 vs 冒泡

<!-- HTML 结构 -->
<div id="outer">
  <ul id="list">
    <li>项目 1</li>
    <li>项目 2</li>
    <li>项目 3</li>
  </ul>
  <button id="add">添加项目</button>
</div>

<script>
// 事件委托:监听 ul,而不是每个 li
// 新增的 li 也会自动响应(因为事件冒泡到 ul)
document.getElementById('list').addEventListener('click', (e) => {
  if (e.target.tagName === 'LI') {
    console.log('点击了:', e.target.textContent, ' | 实际监听在:', e.currentTarget);
  }
});

document.getElementById('add').addEventListener('click', () => {
  const li = document.createElement('li');
  li.textContent = '项目 ' + Date.now();
  document.getElementById('list').appendChild(li);  // 新 li 自动有 click 响应!
});

// 捕获 vs 冒泡对比
const outer = document.getElementById('outer');
outer.addEventListener('click', () => console.log('冒泡阶段触发 (默认)'));
outer.addEventListener('click', () => console.log('捕获阶段触发'), true); // true = 捕获
// 点击 li 时输出顺序:捕获阶段触发 → 冒泡阶段触发
</script>
关键启示:事件委托的核心价值——① 减少监听器数量(100 个 li 只需 1 个监听器);② 自动支持动态添加的元素(新 li 无需重新绑定)。
场景:表格行点击、下拉菜单项、无限滚动列表等。

第二部分:结构图(三门语言各自由什么组成)

3.1 组件图 — 三者与浏览器引擎的关系

浏览器内部由多个引擎协作:渲染引擎(Blink/WebKit/Gecko)负责 HTML/CSS 解析与渲染,JS 引擎(V8/JavaScriptCore/SpiderMonkey)负责执行脚本。DOM(Document Object Model,文档对象模型)/ BOM(Browser Object Model,浏览器对象模型)API 不属于 JS 语言本身,而是浏览器提供的宿主能力——这也是 Node.js 里没有 document 的原因。

图 3.1-1: 浏览器引擎组件图

PlantUML diagram

专业术语

Rendering Engine (渲染引擎)
浏览器的核心子系统,负责 HTML 解析 (DOM)、CSS 解析 (CSSOM)、Layout、Paint 和 Composite。主流引擎:Blink (Chrome/Edge)、WebKit (Safari)、Gecko (Firefox)。
JS Engine (JS 引擎)
独立的脚本执行环境,负责解析 JS 源码 → AST → 字节码 → 机器码的完整流程。主流引擎:V8 (Chrome/Node/Deno)、JavaScriptCore (Safari/Bun)、SpiderMonkey (Firefox)。
Web API (宿主对象)
浏览器提供的 API,不属于 ECMAScript 语言规范。包括 DOM API (document)、BOM API (window、fetch、setTimeout、localStorage) 等。Node.js 没有 DOM/BOM。
Event Loop (事件循环)
协调 JS 单线程与异步操作的调度机制。负责管理调用栈、任务队列(宏/微任务)和渲染时机,使 JS 能在不阻塞主线程的情况下处理网络请求、定时器等异步操作。
JIT (Just-In-Time Compilation)
JS 引擎在运行时将热点代码编译为机器码的优化技术。V8 的 Ignition (解释器) + TurboFan (JIT 编译器) 是典型的两层编译架构。

案例演示:一次点击背后的各引擎协作

// 假设 HTML 中有 <button id="btn">点我</button>

// 步骤 1: JS 引擎 (V8) 执行这段脚本
const btn = document.querySelector('#btn');   // → 调用 DOM API

// 步骤 2: DOM API (Web API) 在渲染引擎中查找元素
//         Blink 遍历 DOM 树找到 #btn,返回其引用

// 步骤 3: JS 通过 DOM API 挂载监听器
btn.addEventListener('click', async () => {
  // 步骤 5: 用户点击 → Event Loop 调这个回调回 V8 执行
  const res = await fetch('/api/data');        // → BOM API (fetch)
  // 步骤 6: fetch 由网络线程处理,不阻塞 JS 主线程
  //         请求完成后,Promise resolve → 微任务入队
  //         Event Loop 调度微任务 → V8 继续执行
  const data = await res.json();

  btn.textContent = data.label;               // → 修改 DOM
  // 步骤 7: 渲染引擎收到 DOM 变更 → 触发重绘/回流
  //         Blink: Layout → Paint → Composite → 屏幕更新
});

// 步骤 4: JS 执行完 → 调用栈清空 → 等待事件
// 步骤 5-7 循环发生在用户交互时
核心理解:JS 引擎 (V8) 本身不处理 DOM、网络、定时器——这些都是浏览器 Web API 层提供的。V8 只负责 "调用" 和 "被回调",调度全靠 Event Loop。
浏览器渲染引擎JS 引擎
Chrome / Edge / OperaBlinkV8(Node.js/Deno 也用它)
SafariWebKitJavaScriptCore(Bun 用它)
FirefoxGeckoSpiderMonkey

3.2 HTML — 文档结构与 DOM 类图

3.2.1 文档骨架

<!DOCTYPE html>                <!-- 声明标准模式,防止怪异模式 -->
<html lang="zh-CN">
<head>                          <!-- 元信息:不渲染 -->
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>页面标题</title>
  <link rel="stylesheet" href="style.css">   <!-- 挂载 CSS -->
</head>
<body>                          <!-- 可见内容 -->
  <h1>标题</h1>
  <script src="app.js" defer></script>       <!-- 挂载 JS -->
</body>
</html>

3.2.2 DOM 类图 — HTML 在内存中的运行时形态

HTML 文本被解析后,每个标签变成一个对象节点,全部继承自 Node;而 Node 又继承 EventTarget——所以任何节点都能 addEventListener。JS 操作页面,本质是操作这棵对象树。

图 3.2-1: DOM 节点类型类图

PlantUML diagram

专业术语

EventTarget
DOM 的根类,提供事件监听能力(addEventListener / removeEventListener / dispatchEvent)。window、XMLHttpRequest 等非节点对象也继承它。
Node
DOM 树中所有节点的基类,提供父子兄弟关系操作(parentNode、childNodes、appendChild、removeChild 等)。
Element
代表 HTML/SVG 标签元素,提供属性操作(getAttribute、classList、innerHTML)。JS 操作页面时最常打交道的就是 Element。
HTMLElement
所有 HTML 元素的基础类(style、dataset、click())。每种具体标签(div、input、canvas)都有对应的子类,带有特定属性和方法。
Document
代表整个 HTML 文档的根节点(document),提供查询方法(querySelector、getElementById)和创建节点方法(createElement)。

案例演示:DOM 各级类的真实操作

// Document — 文档根节点
const doc = document;
console.log(doc.constructor.name);           // "HTMLDocument" (继承 Document)
const body = doc.querySelector('body');      // Document 提供查询方法
const div = doc.createElement('div');        // Document 提供创建方法

// Element / HTMLElement — 标签元素
div.className = 'box';                       // Element 属性
div.classList.add('active');                 // Element 方法
div.style.background = '#eee';               // HTMLElement.style
div.dataset.id = '123';                      // HTMLElement.dataset
div.innerHTML = '<span>hello</span>';  // Element.innerHTML

// Node — 所有节点的基类
body.appendChild(div);                       // Node.appendChild
console.log(div.parentNode === body);        // Node.parentNode
console.log(div.childNodes.length);          // Node.childNodes (含 Text 节点)

// EventTarget — 事件能力
div.addEventListener('click', (e) => {
  console.log('事件目标:', e.target);         // EventTarget 提供
  console.log('节点类型:', e.target.nodeType); // 1=Element, 3=Text, 8=Comment
});
继承链路:div (HTMLDivElement) → HTMLElement → Element → Node → EventTarget —— 所以一个 div 同时拥有事件能力、树操作、属性操作、样式操作。

3.3 CSS — 选择器 / 盒模型 / 层叠

3.3.1 一条规则的解剖

/*  选择器        声明块  */
.card:hover {              /* 类选择器 + 伪类 */
  color: #333;             /* 属性: 值; */
  padding: 16px;
}

3.3.2 层叠 (Cascade) — CSS 的 "C"

多条规则命中同一元素时,按以下顺序裁决,这是 CSS 最核心的机制:

图 3.3-1: 层叠优先级判定活动图

PlantUML diagram

专业术语

Cascade (层叠)
CSS 的 "C" 代表的核心机制:当多条规则命中同一元素时,按 "来源 → 特异性 → 书写顺序" 的优先级裁决最终生效的声明。
Specificity (特异性 / 权重)
衡量选择器优先级的数值,记作 (ID数, 类数, 元素数)。按位比较,(1,0,0) > (0,99,99)。行内 style 高于所有选择器,* 通配符和继承值最低。
!important
将单条声明的优先级提升到最高层级,覆盖一切(除非另一条也有 !important)。会逆转常规的层叠顺序,应尽量避免使用。
来源优先级 (Origin)
CSS 声明的三层来源:用户代理样式(浏览器默认)< 用户样式 < 作者样式(开发者写的)。!important 存在时顺序反转 —— 用户的 !important 最高。
行内样式 (Inline Style)
直接写在 HTML 的 style="" 属性中的声明,特异性高于所有外部/内部选择器。它是特异性计算中的一个独立层级。

案例演示:同元素多条规则,谁生效?

<!-- HTML -->
<div id="main" class="box content" style="color: purple;">
  这段文字最终是什么颜色?
</div>

<!-- CSS -->
<style>
/* ① 来源: 作者样式(浏览器默认用户代理样式优先级最低,忽略) */

/* ② 特异性: (0,0,1) — 1 个元素选择器 */
div { color: red; }

/* ③ 特异性: (0,1,0) — 1 个类选择器 */
.box { color: green; }
.content { color: yellow; }   // 与 .box 同特异性,后写覆盖 → yellow

/* ④ 特异性: (1,0,0) — 1 个 ID 选择器 */
#main { color: blue; }

/* ⑤ 行内 style 高于所有选择器 */
/* style="color: purple" → 这是最终生效的颜色! */

/* ⑥ !important 更高,但应避免 */
#main { color: orange !important; }  // 加了 !important → orange 会覆盖 purple
</style>
裁决过程:① 比来源(都是作者样式)→ ② 比特异性 (1,0,0) > (0,1,0) > (0,0,1) → ③ ID 选择器 blue 胜出,但④ 行内 style=purple 高于一切 → ⑤ 除非某条有 !important。
最终生效:去掉行内样式 = blue;有行内样式 = purple;有 !important = orange。
特异性 (Specificity)示例记作 (a,b,c)
ID 选择器#nav(1,0,0)
类 / 属性 / 伪类.btn [type=text] :hover(0,1,0)
元素 / 伪元素div ::before(0,0,1)

比较时按位比大小:(1,0,0) > (0,99,99)。行内 style="" 高于一切选择器,!important 再高一层(应避免使用)。

3.3.3 盒模型 (Box Model)

每个元素都是一个盒子:content + padding + border + margin。默认 width 只算 content(content-box),现代做法是全局改为 border-box 让 width 包含 padding 和 border,所见即所得:

*, *::before, *::after { box-sizing: border-box; }  /* 现代 CSS 第一行 */

3.4 JS — 类型 / 原型链 / 模块化

3.4.1 类型系统

分类类型说明
原始类型(7 种,按值传递)string number boolean undefined null symbol bigint不可变,存栈
对象类型(按引用传递)Object(含 Array、Function、Date、Map、Set…)函数是一等公民,可赋值/传参/返回

3.4.2 原型链 — JS 面向对象的本质

JS 没有传统类继承,class 只是语法糖:每个对象有隐藏属性 [[Prototype]],读属性时沿链向上查找直到 null。这就是为什么随手创建的数组能调用 map()——方法在 Array.prototype 上。

图 3.4-1: 原型链类图

PlantUML diagram

专业术语

Prototype (原型)
JS 中每个对象都有一个内部属性 [[Prototype]](可通过 __proto__ 或 Object.getPrototypeOf 访问),指向它的原型对象。原型对象自身也可以有原型,形成链。
Prototype Chain (原型链)
JS 实现属性查找和继承的核心机制:读属性时先从对象自身查找,找不到就沿 [[Prototype]] 链向上,直到 null。这就是为什么所有数组都有 map()、所有对象都有 toString()。
Constructor (构造函数)
带 prototype 属性的函数对象(包括 class)。当用 new 调用时,返回的新对象的 [[Prototype]] 会指向构造函数的 prototype 属性。
Prototypal Inheritance (原型继承)
JS 的继承模型(区别于 Java/C++ 的类继承):对象直接继承对象。ES6 的 class extends 只是原型继承的语法糖,底层仍然是原型链。
Object.prototype
几乎所有对象的原型链终点(再往上是 null)。它提供了 toString()、hasOwnProperty()、valueOf() 等共方法。

案例演示:亲手验证原型链查找

// 创建不同类型的数据,追踪 [[Prototype]] 链
const arr  = [1, 2, 3];
const obj  = { a: 1 };
const str  = 'hello';
const fn   = function() {};
const date = new Date();

// 验证原型链:所有对象最终都追溯到 Object.prototype
console.log(arr.__proto__                    === Array.prototype);    // true
console.log(arr.__proto__.__proto__          === Object.prototype);   // true
console.log(arr.__proto__.__proto__.__proto__ === null);              // true — 链终点

console.log(fn.__proto__  === Function.prototype);  // true — 函数的直接原型
console.log(date.__proto__ === Date.prototype);     // true

// 属性查找过程演示
arr.myMethod = '自身属性';
console.log(arr.myMethod);             // '自身属性' — 第 1 步找到
console.log(arr.map);                  // ƒ map() — 自身没有,沿链找到 Array.prototype
console.log(arr.toString);             // ƒ toString() — Array 没有,继续到 Object.prototype
console.log(arr.xyz);                  // undefined — 查遍全链都没有
查找路径:arr.自身 → Array.prototype → Object.prototype → null,每一步找不到就继续向上。
类比:原型链就像作用域链——变量先从当前作用域找,找不到往外层,直到全局。原型链就是 "属性的作用域链"。
// class 只是语法糖
class Dog {
  constructor(name) { this.name = name; }
  bark() { return `${this.name} says woof`; }
}
const d = new Dog('旺财');
console.log(d.__proto__ === Dog.prototype);            // true — 和 function 原型一模一样
console.log(d.bark === Dog.prototype.bark);            // true — 方法在 prototype 上,不在实例
console.log(Dog.prototype.__proto__ === Object.prototype); // true

3.4.3 模块化 — CJS vs ESM

ESM 是模块化语法规范(不是标准库):规定代码怎么拆文件、怎么 import/export、宿主怎么加载解析。它统一了浏览器与 Node 两个世界,取代了 Node 私有的 CommonJS:

维度CommonJS (CJS)ES Modules (ESM)
语法require() / module.exportsimport / export
标准Node.js 社区约定ECMA-262 语言标准(ES2015 引入)
加载时机运行时、同步、动态编译期静态分析(支持 Tree Shaking),异步加载
浏览器支持不支持(需打包器转换)原生支持 <script type="module">
值语义导出值的拷贝导出值的只读实时绑定 (live binding)
现状Node 存量生态唯一官方标准,Vite 等现代工具的基石

3.5 前端组件化演进

组件(Component)是前端 UI 领域的核心抽象:把一段可复用的 HTML + CSS + JS 封装成一个独立单元,对外只暴露 props(入参) 和 事件(出参)。本节梳理"组件"概念在前端的起源,并用具体代码演示三种主流实现方式。

3.5.1 历史时间线

时期里程碑意义
2011-2013Google 推动 Web Components 标准(Custom Elements、Shadow DOM、HTML Templates)浏览器原生组件化方案开始成形
2013React 开源发布引入"组件 = 函数 + 状态"心智模型,声明式 UI 成为主流
2014Vue 1.x 发布以 SFC(单文件组件)降低组件化门槛
2018+Web Components 获得所有主流浏览器原生支持框架无关的跨平台组件标准成熟

3.5.2 三种实现方式 — 同一个计数器组件

方式一:Web Components 原生组件(Custom Elements + Shadow DOM)

<!-- HTML 中使用 -->
<my-counter initial="5"></my-counter>

<script>
class MyCounter extends HTMLElement {
  connectedCallback() {
    // 从属性读入参 (props)
    this._count = Number(this.getAttribute('initial')) || 0;
    // Shadow DOM:封装内部结构与样式,外部无法污染
    this.attachShadow({ mode: 'open' });
    this.shadowRoot.innerHTML = `
      <style>button { font-size: 20px; } </style>
      <button>-</button>
      <span>${this._count}</span>
      <button>+</button>`;
    this.shadowRoot.querySelectorAll('button')[0]
      .addEventListener('click', () => this.set(this._count - 1));
    this.shadowRoot.querySelectorAll('button')[1]
      .addEventListener('click', () => this.set(this._count + 1));
  }
  set(n) {
    this._count = n;
    this.shadowRoot.querySelector('span').textContent = n;
  }
}
customElements.define('my-counter', MyCounter);
</script>
要点:① customElements.define() 注册新标签;② 组件名必须含连字符(my-counter)避免与原生标签冲突;③ attachShadow 实现样式与结构的真正封装(外部 CSS 无法穿透)。

方式二:React 函数组件(组件 = 函数 + 状态)

// Counter.jsx
import { useState } from 'react';

export default function Counter({ initial = 0 }) {  // props 入参
  const [count, setCount] = useState(initial);       // 内部状态
  return (
    <div className="counter">
      <button onClick={() => setCount(c => c - 1)}>-</button>
      <span>{count}</span>
      <button onClick={() => setCount(c => c + 1)}>+</button>
    </div>
  );
}

// 使用:<Counter initial={5} />
要点:组件就是一个普通函数,props 是入参、useState 是内部状态;状态一变,函数重新执行、视图自动更新(声明式)。

方式三:Vue 单文件组件(SFC)

<!-- Counter.vue -->
<template>                              <!-- 结构 -->
  <div class="counter">
    <button @click="count--">-</button>
    <span>{{ count }}</span>
    <button @click="count++">+</button>
  </div>
</template>

<script setup>                          <!-- 逻辑 -->
import { ref } from 'vue';
const props = defineProps<{ initial: number }>();  // props 入参
const count = ref(props.initial ?? 0);               // 内部状态
</script>

<style scoped>                          <!-- 样式 (scoped 自动隔离) -->
.counter { display: flex; gap: 8px; }
</style>
要点:一个 .vue 文件 = 模板 + 逻辑 + 样式三合一;scoped 关键字让样式自动按组件隔离,比原生 Shadow DOM 更省心。

3.5.3 传统 vs 组件化 — 同一个用户卡片

传统方式:手动操作 DOM

// 每渲染一张卡片都要重复这段命令式代码
function renderUserCard(user) {
  const card = document.createElement('div');
  card.className = 'card';
  const name = document.createElement('h3');
  name.textContent = user.name;                 // 潜在 XSS,需转义
  const btn = document.createElement('button');
  btn.textContent = '关注';
  let followed = false;                          // 状态散落在外部变量
  btn.onclick = () => {
    followed = !followed;
    btn.textContent = followed ? '已关注' : '关注';
  };
  card.append(name, btn);
  document.querySelector('#list').appendChild(card);
}
// 100 个用户 = 手动调 100 次,状态全靠自己记

组件化方式:声明 + 复用

// Vue 里写一次 UserCard,到处复用
<UserCard v-for="u in users" :user="u" />

// 关注状态是组件内部的事,外部无需关心
<script setup>
const props = defineProps<{ user: User }>();
const followed = ref(false);
</script>
核心价值:① 封装——HTML/CSS/JS 收进一个单元,状态不外泄;② 复用——写一次用 N 处,只传不同 props;③ 声明式——只声明"数据长什么样",浏览器/框架负责更新 DOM,不再手动 appendChild。

3.5.4 什么是状态(State)

状态就是随时间变化、并驱动界面显示的数据。组件化框架的核心工作,就是把"状态"和"视图"绑定:状态一变,视图自动重新渲染。

案例演示:状态 vs 普通变量

// 状态:会变化 + 驱动视图
const [count, setCount] = useState(0);   // count 是状态
setCount(5);                              // 改状态 → 界面自动显示 5

// 普通变量:变了界面不知道
let count = 0;                            // 不是状态,改了也没人管
count = 5;                                // 界面毫无反应
判据:一个数据只要满足 ① 会变 ② 界面依赖它显示,就该放进状态。
典型状态:计数器 count、登录用户 userInfo、表单输入 inputValue、列表 list、加载中 loading。

对照:状态驱动视图 vs 手动操作 DOM

// ❌ 传统命令式:改数据 + 手动同步界面(易漏、易错)
let count = 0;
count++;
document.querySelector('#num').textContent = count;  // 忘了这行就不同步

// ✅ 状态驱动(React / Vue):只改状态,界面自动更新
const [count, setCount] = useState(0);
setCount(count + 1);   // 浏览器自动重渲染,无需手动操作 DOM
一句话:state 是"会变的数据",视图是它的影子——改状态 = 自动更新界面。
这正是上一节"声明式"的底层机制:开发者只声明"数据长什么样",更新 DOM 的脏活交给框架。
组件概念的本源
组件不是框架发明的:软件工程的"组件化复用"思想可追溯到 1968 年 McIlroy 的"量产软件组件";前端只是在 2011-2014 年把它落到 UI 层。如今 Web Components 是浏览器原生标准,React/Vue 是框架层实现,三者殊途同归:都是"可复用、可组合、接口清晰的独立单元"。

第三部分:速查手册

4.1 HTML 语义化标签速查

分类标签用途
页面结构<header> <nav> <main> <article> <section> <aside> <footer>替代无意义的 <div>,利于 SEO 和无障碍
文本<h1>~<h6> <p> <strong> <em> <blockquote> <code>标题层级唯一 h1;strong/em 有语义,b/i 无
列表/表格<ul> <ol> <li> <dl> <table> <thead> <tbody>表格只用于数据,不用于布局
媒体<img> <picture> <video> <audio> <canvas> <svg>img 必写 alt;懒加载用 loading="lazy"
表单<form> <input> <label> <select> <textarea> <button>label 用 for 关联 input;input 有 20+ 种 type
交互(免 JS)<details> <summary> <dialog>原生折叠面板和模态框

4.2 CSS 布局速查

方案维度适用场景核心属性
Flexbox一维(行或列)导航栏、居中、等分、工具栏display:flex justify-content align-items gap flex:1
Grid二维(行+列)整页骨架、卡片墙、仪表盘display:grid grid-template-columns gap grid-area
普通流 + position—局部微调、悬浮元素position: relative/absolute/fixed/sticky
/* 万能居中 */
.parent { display: flex; justify-content: center; align-items: center; }

/* 响应式卡片墙:自动换行,每列最少 240px */
.wall { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 16px; }

/* 圣杯布局 */
.page { display: grid; grid-template: "header header" auto "side main" 1fr "footer footer" auto / 220px 1fr; }

/* 响应式断点 */
@media (max-width: 800px) { .sidebar { display: none; } }

4.3 JS 核心语法速查

// 变量:const 优先,需要重新赋值才用 let,永远不用 var
const list = [1, 2, 3];

// 解构 & 展开
const { name, age = 18 } = user;
const merged = [...a, ...b];

// 箭头函数(无自己的 this)
const double = (x) => x * 2;

// 数组三板斧
list.map(x => x * 2).filter(x => x > 2).reduce((s, x) => s + x, 0);

// 异步:async/await 是 Promise 的语法糖
async function load() {
  try {
    const res = await fetch('/api/data');
    if (!res.ok) throw new Error(res.status);
    return await res.json();
  } catch (e) {
    console.error('请求失败', e);
  }
}

// DOM 四件套:查、改、建、听
const el = document.querySelector('#app');
el.textContent = 'hello';                 // 改文本(安全,不解析 HTML)
el.classList.toggle('active');            // 改样式:操作 class 而非行内 style
const li = document.createElement('li');  // 建节点
el.addEventListener('click', (e) => {});  // 监听事件

4.4 三者协作完整示例 — 计数器

一个文件展示三层分工:HTML 定义结构和挂载点,CSS 负责外观和状态样式,JS 监听事件并更新 DOM,浏览器自动重渲染。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<style>
  /* CSS:表现层 */
  .counter { display: flex; gap: 12px; align-items: center; font-size: 24px; }
  button { padding: 8px 20px; border: none; border-radius: 8px;
           background: #1572b6; color: #fff; cursor: pointer; }
  button:hover { opacity: 0.85; }
  .num.warn { color: #e34f26; font-weight: 700; }   /* 状态样式由 JS 切换 */
</style>
</head>
<body>
  <!-- HTML:结构层 -->
  <div class="counter">
    <button id="dec">-</button>
    <span class="num" id="num">0</span>
    <button id="inc">+</button>
  </div>

  <script>
    // JS:行为层 —— 状态驱动视图
    let count = 0;
    const num = document.querySelector('#num');
    function render() {
      num.textContent = count;
      num.classList.toggle('warn', count >= 10);  // JS 只切 class,样式交给 CSS
    }
    document.querySelector('#inc').addEventListener('click', () => { count++; render(); });
    document.querySelector('#dec').addEventListener('click', () => { count--; render(); });
  </script>
</body>
</html>
这个 20 行的模式就是所有前端框架的雏形:状态(count)→ 渲染函数(render)→ 事件驱动更新。React/Vue 做的事情,本质是把"状态变化后该更新哪些 DOM"自动化了。

4.5 常见坑与最佳实践

坑原因正确做法
0.1 + 0.2 !== 0.3IEEE 754 浮点数精度金额用整数分存储,或 toFixed 显示
== 出现诡异结果隐式类型转换('' == 0 为 true)永远用 ===
循环里 innerHTML 拼接卡顿每次赋值都触发重新解析+回流拼完一次性赋值,或用 DocumentFragment
动画掉帧动了 width/left 等触发回流只动 transform / opacity(合成层)
用户输入直接插入 innerHTMLXSS 注入用 textContent,或严格转义
样式全写行内 / JS 里改 style结构表现行为耦合,难维护JS 只切换 class,样式集中在 CSS
div 套 div 到底无语义,无障碍差优先语义化标签(4.1 节)
学习路径建议:HTML(1 周,重点表单和语义化)→ CSS(2-3 周,重点盒模型 + Flex/Grid)→ JS(1-2 月,重点异步和 DOM)→ 然后才是框架(React/Vue)和构建工具(Vite,见本文 Vite 系统架构章节)。权威参考永远首选 MDN。

第四部分:CSS 系统深入

来源:MDN Web Docs · W3C CSS WG · CSS Editor's Drafts

CSS(Cascading Style Sheets,级联样式表)是一种描述 HTML/XML/SVG 文档呈现方式的声明式样式语言。它是开放 Web 三大核心技术之一(HTML 结构 + CSS 表现 + JS 行为),由 W3C CSS 工作组制定规范。自 CSS2.1 之后,CSS 不再有统一版本号,而是拆分为独立模块各自演进(如 Selectors Level 4、Color Level 5、Grid Level 2 等),通过年度 CSS Snapshot 记录各模块成熟度。

PlantUML diagram
图 1: CSS 系统全景——三个维度

5.1 概述

CSS 的设计哲学是关注点分离:内容(HTML)与表现(CSS)解耦。其核心机制围绕三个关键词展开:

机制含义关键概念
Cascade(层叠)多来源样式冲突时的裁决规则来源、特异性、书写顺序、!important、@layer
Inheritance(继承)某些属性自动从父元素传递inherit / initial / unset / revert
Specificity(特异性)选择器优先级的量化指标ID > Class > Type (0,0,0 三元组)
关键认知:CSS 不是"CSS3"也不是"CSS4"——CSS2.1 之后规范按模块独立演进。目前 CSS 包含 70+ 活跃模块,每个模块有独立版本号(Level 1~6)。W3C 每年发布 CSS Snapshot 记录所有模块状态。

5.2 行为分析(系统做什么)

5.2.1 用例图:CSS 系统的参与者与功能

CSS 系统有多个参与者:设计师编写样式、开发者维护工具链、浏览器引擎解析渲染、最终用户阅读页面。

PlantUML diagram
图 2.1: CSS 系统用例图

5.2.2 活动图:CSS 渲染流水线

浏览器将 CSS 文本转换为屏幕像素的完整流水线:解析 CSS → 构建 CSSOM → 合并 Render Tree → Layout → Paint → Composite。每一步都可能成为性能瓶颈。

PlantUML diagram
图 2.2: CSS 渲染流水线活动图

5.2.3 活动图:层叠优先级判定

多条 CSS 规则命中同一元素时,浏览器按什么顺序裁决最终生效值?这是 CSS 的"C"——Cascade 的核心流程。

PlantUML diagram
图 2.3: 层叠优先级判定活动图

5.2.4 活动图:选择器匹配流程

浏览器如何为 DOM 中的每个元素找到匹配的 CSS 规则?注意浏览器是从右向左匹配的,以快速排除不匹配元素。

PlantUML diagram
图 2.4: 选择器匹配活动图

5.2.5 活动图:布局计算流程

当 Render Tree 构建完成后,浏览器计算每个元素的几何位置和尺寸——这就是 Layout(布局/回流)阶段。

PlantUML diagram
图 2.5: 布局计算活动图

5.2.6 序列图:动画与过渡时序

CSS 动画和过渡的触发、帧计算、合成渲染的完整时序。

PlantUML diagram
图 2.6: CSS 动画/过渡时序图

5.3 结构分析(系统由什么组成)

5.3.1 包图:CSS 规范模块组织

CSS 规范按功能领域拆分为独立模块,各自有独立 Level 版本号。以下是主要模块的组织结构。

PlantUML diagram
图 3.1: CSS 规范模块包图

5.3.2 组件图:浏览器渲染引擎中的 CSS 子系统

CSS 引擎在浏览器渲染引擎(Blink/WebKit/Gecko)中的组件架构。

PlantUML diagram
图 3.2: 浏览器渲染引擎 CSS 子系统组件图

5.3.3 类图:CSSOM 核心对象模型

CSS Object Model(CSSOM)是 JS 操作样式的 API 层,是 CSS 在内存中的对象表示。

PlantUML diagram
图 3.3: CSSOM 核心类图

5.3.4 结构图:CSS 盒模型

每个元素在 CSS 中都是一个矩形盒子,由 content → padding → border → margin 四层嵌套组成。

PlantUML diagram
图 3.4: CSS 盒模型结构图

5.3.5 部署图:不同媒介的渲染上下文

CSS 不仅服务于屏幕浏览器,还覆盖打印、语音、电子纸等多种媒介。

PlantUML diagram
图 3.5: CSS 渲染上下文部署图

5.4 层叠机制详解

5.4.1 层叠总览

当多条 CSS 规则命中同一元素时,浏览器按以下 6 层优先级裁决(由低到高):

  1. 用户代理样式(浏览器默认)——如默认的 display: block、margin: 8px
  2. 用户样式——用户在浏览器设置中自定义的样式
  3. 作者样式中的 @layer——按声明顺序,后声明的层优先级更高
  4. 作者样式中的无层级(normal)规则——特异性裁决
  5. 作者样式中的 !important——反转常规优先级
  6. 用户样式中的 !important / 用户代理 !important——最高优先级(无障碍保障)

5.4.2 特异性计算

特异性记作 (A, B, C) 三元组:

AID 选择器数量
B类选择器、属性选择器、伪类的数量
C元素选择器、伪元素的数量

比较规则:先比 A,相同时比 B,再相同时比 C。注意 (1,0,0) > (0,999,999)——A 优先于 B 优先于 C。

特殊规则:

Demo: 特异性对比
/* (0,0,1) */    p        { color: blue; }
/* (0,1,0) */    .intro   { color: green; }
/* (1,0,0) */    #hero    { color: orange; }
/* 结果: #hero 最高优先级 */
(0,0,1) < (0,1,0) < (1,0,0) — 每级之间的差距是无限的
查看源码
/* (0,0,1) → 1 个元素选择器 */
p { color: blue; }

/* (0,1,0) → 1 个类选择器 */
.intro { color: green; }  /* 优先级高于 p */

/* (1,0,0) → 1 个 ID 选择器 */
#hero { color: orange; }  /* 优先级高于 .intro */

5.4.3 继承机制

CSS 属性分为两类:

四个通用关键字控制继承:inherit(强制继承)、initial(重置为初始值)、unset(可继承属性和 inherit 相同,不可继承属性和 initial 相同)、revert(回退到上一层来源的值)。

5.4.4 属性值处理流程

一个属性的最终值经历了 6 个阶段的转换:

  1. 声明值(Declared Value)——样式表中写了什么(可能多条声明命中同一元素)
  2. 层叠值(Cascaded Value)——层叠裁决后的赢家(含 !important 和 @layer)
  3. 指定值(Specified Value)——若无层叠值则取继承值或初始值
  4. 计算值(Computed Value)——相对单位(em、%)解析为绝对但不依赖于布局的值
  5. 使用值(Used Value)——布局计算后的最终值(如 width: auto 计算为具体像素)
  6. 实际值(Actual Value)——受限于设备精度(如浮点像素取整)

5.4.5 @layer 级联层

CSS Cascade Level 5 引入 @layer 规则,允许开发者显式分层管理样式优先级:

/* 声明层顺序——越后声明优先级越高 */
@layer reset, base, components, utilities;

@layer reset {
  * { margin: 0; padding: 0; }
}

@layer base {
  h1 { font-size: 2rem; }
}

@layer components {
  .card { border: 1px solid #ddd; }
}

@layer utilities {
  .mt-4 { margin-top: 1rem; }  /* 优先级高于 reset/base/components */
}
Demo: @layer 层叠验证
后声明的 high 层覆盖了 low 层 (即使 high 声明在文件中位置靠后)
查看源码
@layer low  { .box { background: #fdd; } }
@layer high { .box { background: #d4edda; } }
/* 虽然 low 在前 high 在后,但 @layer 声明顺序决定了优先级 */

5.5 布局系统

5.5.1 格式化上下文

CSS 布局的基础是格式化上下文——一个区域内的元素按特定规则排列。主要有以下类型:

上下文类型触发方式作用
BFC(块级格式化上下文)display: flow-root / overflow: hidden / float / position: absolute独立块布局区域,防止外边距折叠、包含浮动
IFC(行内格式化上下文)块容器内默认存在行内级元素在水平方向排列
Flex Formatting Contextdisplay: flex / inline-flex弹性盒子布局
Grid Formatting Contextdisplay: grid / inline-grid二维网格布局

5.5.2 常规流布局(Flow Layout)

默认布局方式。块级元素垂直堆叠,行内元素水平排列。核心概念:

5.5.3 Flexbox 弹性布局

CSS Flexible Box Layout(Level 1)提供一维方向的灵活布局。核心模型:

5.5.4 Grid 网格布局

CSS Grid Layout(Level 1/2)提供二维网格布局能力,是目前最强的 CSS 布局系统:

5.5.5 定位布局

CSS Positioned Layout 允许元素脱离常规流进行精确定位:

定位方式参考系是否脱离常规流
static(默认)常规流位置否
relative自身原位置否(保留原位置空间)
absolute最近的定位祖先(非 static)是
fixed视口是
sticky滚动容器(滚动到阈值后固定)否(切换)

核心概念:

Demo: Flexbox + Grid 布局

Flexbox (一维)

A
B (grow)
C

Grid (二维)

Header (span 2)
Side
Main
Foot
查看源码
/* Flexbox */
.flex { display: flex; gap: 8px; }
.flex .item:nth-child(2) { flex-grow: 1; }

/* Grid */
.grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.grid .header { grid-column: 1 / 3; }  /* 跨两列 */

5.6 动画系统

CSS 提供三层动画能力,从简单到复杂:

机制触发方式控制力典型场景
Transition(过渡)属性值变化时自动触发(如 :hover)简单(from → to)按钮悬停变色、菜单滑入
Animation(动画)通过 @keyframes 定义,可自动播放复杂(多关键帧、循环、反向)加载动画、无限循环动效
Scroll-driven Animations滚动位置驱动时间线与滚动绑定滚动视差、进度指示器

关键性能原则:只动画 transform 和 opacity——它们只触发 Composite 阶段,不走 Layout/Paint,可保持 60fps。使用 will-change 提前提示浏览器创建合成层。

动画性能分级:
- 最便宜(仅 Composite):transform、opacity
- 中等(Paint + Composite):color、background、box-shadow
- 最贵(Layout + Paint + Composite):width、height、top、left、margin
Demo: Transition + Animation

Hover 查看 Transition

@keyframes 自动播放

查看源码
/* Transition */
.btn { transition: all 0.3s; }
.btn:hover { background: #e34f26; transform: translateY(-2px); }

/* Animation */
@keyframes pulse {
  0%, 100% { transform: scale(1); opacity: 1; }
  50% { transform: scale(1.3); opacity: 0.6; }
}
.dot { animation: pulse 1.5s ease-in-out infinite; }

5.7 选择器系统

选择器是 CSS 的寻址系统,决定样式应用到哪些元素。按规范演进分为多个 Level:

Level新增功能状态
Level 3 (REC)属性选择器、伪类、组合器全面支持
Level 4 (WD):is() :where() :has() :not() 复合参数、:focus-visible、:focus-within、逻辑组合器 ||主流浏览器已支持
Level 5 (WD):state()、:open、:closed、自定义状态实验阶段

选择器分类:

:has() 是"父选择器":Level 4 引入的 :has() 是革命性的——它可以基于后代元素反向选择父元素,例如 div:has(> img) 选择包含直接子 img 的 div。

Demo: 选择器效果
普通卡片 (无图片)
🖼 图片卡片 — 边框自动加粗

:nth-child 斑马纹

  • 第 1 行
  • 第 2 行
  • 第 3 行
  • 第 4 行
  • 第 5 行
查看源码
/* :has() — 父选择器 */
.card:has(img) { border-color: #1572b6; border-width: 2px; }

/* :nth-child — 斑马纹 */
li:nth-child(odd) { background: #e8f0fe; }

5.8 响应式设计

CSS 响应式设计的核心工具链:

/* 现代响应式组合拳 */
.card {
  width: clamp(300px, 50%, 600px);           /* 限制不超出范围 */
  padding: clamp(1rem, 2vw, 3rem);           /* 自适应内边距 */
  font-size: clamp(1rem, 0.5rem + 1vw, 2rem); /* 流体排版 */
}
Demo: clamp() 流体字号 — 缩放浏览器窗口查看效果

这段文字使用 font-size: clamp(14px, 1.5vw, 24px), 缩放浏览器宽度可以看到字号平滑变化,但不会超出 14px~24px 范围。

查看源码
.fluid-text {
  font-size: clamp(14px, 1.5vw, 24px);
  /* 最小14px,理想1.5%视口宽度,最大24px */
}

5.9 性能与优化

CSS 性能影响页面渲染的三个阶段:

阶段触发条件优化策略
Layout(回流)改几何属性:width/height/top/left/font-size/display/position批量修改(用 requestAnimationFrame 或 documentFragment);用 transform 替代 top/left
Paint(重绘)改非几何属性:color/background/border/box-shadow/outline减少绘制区域;用 contain: paint 限制绘制范围
Composite(合成)改 transform/opacity用 will-change 提升为合成层;避免过多层(层爆炸)

关键改进:

Demo: content-visibility 对比
性能优化代码示例
/* 跳过屏幕外元素渲染,减少初始 Layout/Paint 开销 */
.off-screen-section {
  content-visibility: auto;
  contain-intrinsic-size: 0 500px;  /* 占位高度,防止滚动条跳动 */
}

/* 将动画元素提升为独立合成层 */
.animated {
  will-change: transform;
  transform: translateZ(0);  /* 强制 GPU 合成 */
}

/* 隔离子树,告诉浏览器该区域独立 */
.widget {
  contain: layout style paint;
}

5.10 生态工具链

CSS 周边工具链覆盖开发、构建、运行时的完整流程:

类别工具作用
预处理器Sass/SCSS, Less, Stylus变量、嵌套、Mixin、函数——编译为普通 CSS
后处理器PostCSS + Autoprefixer插件化 CSS 转换(自动加厂商前缀、未来语法转译)
CSS 框架Tailwind CSS, Bootstrap, Bulma预设 utility class / 预置组件样式(样式层,非完整组件)
CSS-in-JSstyled-components, Emotion, CSS Modules组件作用域样式(React/Vue 生态常用)
构建工具Vite, webpack (css-loader/style-loader)CSS 模块打包、压缩、Tree Shaking
优化工具PurgeCSS, cssnano, Lightning CSS移除未使用 CSS、压缩、极速转译
LintingStylelintCSS 代码规范检查
设计系统CSS 自定义属性(--*)原生变量体系,运行时动态切换主题
/* CSS 自定义属性——原生变量体系 */
:root {
  --color-primary: #2563eb;
  --spacing-unit: 0.25rem;
}

.btn {
  background: var(--color-primary);
  padding: calc(var(--spacing-unit) * 4) calc(var(--spacing-unit) * 8);
}
/* 暗色模式只需覆盖变量 */
[data-theme="dark"] {
  --color-primary: #60a5fa;
}
Demo: CSS 自定义属性 + 主题切换

主题演示区域

背景和文字颜色由 --demo-bg / --demo-fg 控制

查看源码
.box {
  background: var(--demo-bg, #f0f2f5);
  color: var(--demo-fg, #212529);
}
.box.dark { --demo-bg: #1a1a2e; --demo-fg: #e0e0e0; }
/* 切换类名即可换主题,无需重写所有属性 */

5.11 参考来源


第五部分:JavaScript 系统深入

6.1 语言定位

什么是 JavaScript

JavaScript 是一种动态类型原型继承垃圾回收一等函数的编程语言。标准名称为 ECMAScript(ECMA-262),由 TC39 委员会维护。运行于单线程事件循环模型之上,通过 JIT 编译实现高性能执行。

特性说明
范式多范式:命令式、函数式、面向对象、事件驱动
类型系统动态弱类型,7 种原始类型 + Object
继承模型原型链(prototype chain),ES6 引入 class 语法糖
内存管理自动垃圾回收(标记-清除 + 分代收集)
并发模型单线程事件循环 + 异步非阻塞 I/O
标准规范ECMA-262(语言核心)、ECMA-402(国际化 API)
主要引擎V8(Chrome/Node.js)、SpiderMonkey(Firefox)、JavaScriptCore(Safari)

6.2 版本历史

1995 — Brendan Eich 在 Netscape 10 天内创建 Mocha → LiveScript → JavaScript
1996 — Microsoft 推出 JScript,浏览器大战开始
1997 — ECMA-262 第 1 版(ES1)发布,语言标准化
1999 — ES3:正则、try/catch、do-while、switch
2005 — Ajax 兴起,JS 从玩具语言走向正经应用
2008 — Google 发布 V8 引擎(JIT 编译),Chrome 浏览器诞生
2009 — ES5:"use strict"、JSON、Array 高阶方法;Node.js 诞生
2015 — ES6/ES2015:let/const、箭头函数、class、Promise、模块、Map/Set
2017 — ES2017:async/await、SharedArrayBuffer
2020 — ES2020:BigInt、可选链 ?.、空值合并 ??、import()、globalThis
2023 — ES2023:Array findFromLast、Hashbang Grammar、WeakMap Symbol key
2025+ — 年度发布节奏,Temporal API、Pattern Matching 等提案推进中

6.3 生态全景

三层生态结构

层级内容关键项目
语言核心ECMAScript 规范定义的语法、类型、内置对象ECMA-262, ECMA-402
引擎层C++ 实现的 JS 虚拟机,负责解析/编译/执行V8, SpiderMonkey, JavaScriptCore
运行时层提供 I/O、网络、文件系统等宿主 APIBrowser, Node.js, Deno, Bun, Cloudflare Workers

6.4 行为图(系统做什么)

6.4.1 用例图 — 开发者与语言的交互全景

PlantUML diagram
图 1.1.1:JavaScript 用例图 — 开发者视角

6.4.2 活动图 — 源码到可执行代码的解析流水线

PlantUML diagram
图 1.2.1:活动图 — JavaScript 源码解析执行流程

闭包 & 作用域 Demo

闭包 — 函数记住其创建时的作用域

function createCounter(initial = 0) {
  let count = initial;          // 私有变量,外部不可直接访问
  return {
    increment: () => ++count,
    decrement: () => --count,
    getValue: () => count,
  };
}

const c = createCounter(10);
console.log(c.increment()); // 11
console.log(c.increment()); // 12
console.log(c.decrement()); // 11
console.log(c.getValue());  // 11
// console.log(c.count);    // undefined — count 被闭包保护

经典场景 — 防抖 & 节流

function debounce(fn, delay) {
  let timer = null;
  return function (...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

function throttle(fn, interval) {
  let lastTime = 0;
  return function (...args) {
    const now = Date.now();
    if (now - lastTime >= interval) {
      lastTime = now;
      fn.apply(this, args);
    }
  };
}

const onResize = debounce(() => console.log('resized'), 300);
window.addEventListener('resize', onResize);

var vs let/const 的闭包陷阱

for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0);
}
// 输出: 3, 3, 3 — var 没有块级作用域

for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0);
}
// 输出: 0, 1, 2 — let 每次迭代绑定新变量

6.4.3 活动图 — 事件循环(Event Loop)

PlantUML diagram
图 1.3.1:活动图 — 浏览器 & Node.js 事件循环

事件循环可视化 Demo

理解 Event Loop 的执行顺序

运行下面代码,观察 Call Stack → Microtask → Macrotask 的执行顺序:

console.log('1. 同步代码开始');

setTimeout(() => console.log('5. setTimeout 宏任务'), 0);

Promise.resolve()
  .then(() => console.log('3. Promise.then 微任务'))
  .then(() => console.log('4. Promise.then 链式微任务'));

queueMicrotask(() => console.log('2. queueMicrotask 微任务'));

console.log('6. 同步代码结束');

// 输出顺序: 1 → 6 → 2 → 3 → 4 → 5
// 原因: 同步 > 所有微任务 > 一个宏任务 > 重复

嵌套微任务 & 宏任务

console.log('start');

setTimeout(() => {
  console.log('macro 1');
  Promise.resolve().then(() => console.log('micro inside macro'));
}, 0);

Promise.resolve().then(() => {
  console.log('micro 1');
  setTimeout(() => console.log('macro inside micro'), 0);
});

console.log('end');

// 输出: start → end → micro 1 → macro 1 → micro inside macro → macro inside micro

6.4.4 时序图 — V8 JIT 编译流水线

PlantUML diagram
图 1.4.1:时序图 — V8 引擎从冷启动到优化编译的全过程

6.4.5 时序图 — 垃圾回收流程

PlantUML diagram
图 1.5.1:时序图 — V8 Orinoco GC(并发标记 + 并行压缩)

6.4.6 状态机图 — Promise 生命周期

PlantUML diagram
图 1.6.1:状态机图 — Promise/A+ 三态转换模型

Promise / async-await Demo

Promise 基础

function fetchUser(id) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (id > 0) resolve({ id, name: 'User' + id });
      else reject(new Error('Invalid id'));
    }, 100);
  });
}

// 链式调用
fetchUser(1)
  .then(user => {
    console.log(user.name);        // User1
    return fetchUser(2);
  })
  .then(user => console.log(user.name))
  .catch(err => console.error(err))
  .finally(() => console.log('请求结束'));

// 静态方法
Promise.all([fetchUser(1), fetchUser(2)])
  .then(([u1, u2]) => console.log(u1, u2))
  .catch(err => console.error('任一失败则整体 reject', err));

Promise.allSettled([fetchUser(1), fetchUser(-1)])
  .then(results => results.forEach(r => console.log(r.status)));
  // { status: 'fulfilled', value: { id:1, name:'User1' } }
  // { status: 'rejected',  reason: Error('Invalid id') }

Promise.race([fetchUser(1), fetchUser(2)]).then(console.log); // 取最快
Promise.any([fetchUser(-1), fetchUser(2)]).then(console.log); // 取第一个 fulfilled

async / await

async function loadUsers() {
  try {
    const u1 = await fetchUser(1);    // 等待,像同步写法
    const u2 = await fetchUser(2);    // 串行:总耗时 200ms
    return [u1, u2];
  } catch (err) {
    console.error('加载失败', err);
    return [];
  }
}

// 并发 vs 串行
async function loadParallel() {
  const [u1, u2] = await Promise.all([
    fetchUser(1),
    fetchUser(2)
  ]);                                   // 并发:总耗时 100ms
  return [u1, u2];
}

// 顶层 await (ESM 模块中可用)
// const data = await fetch('/api/data');

6.5 结构图(系统由什么组成)

6.5.1 组件图 — 运行环境整体架构

PlantUML diagram
图 2.1.1:组件图 — 浏览器中 JavaScript 与宿主环境的交互

6.5.2 包图 — ECMAScript 规范模块划分

PlantUML diagram
图 2.2.1:包图 — ECMA-262 规范的核心模块组织结构

6.5.3 类图 — V8 引擎核心对象模型

PlantUML diagram
图 2.3.1:类图 — V8 引擎内部核心类型与编译管线

6.5.4 类图 — 原型链体系

PlantUML diagram
图 2.4.1:类图 — JavaScript 对象原型链全景

原型链 & 继承 Demo

原型链基础

const arr = [1, 2, 3];
console.log(arr.__proto__ === Array.prototype);           // true
console.log(Array.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__);                     // null

// arr → Array.prototype → Object.prototype → null

const fn = () => {};
console.log(fn.__proto__ === Function.prototype);            // true
console.log(Function.prototype.__proto__ === Object.prototype); // true
// fn → Function.prototype → Object.prototype → null

构造函数 + 原型继承

function Animal(name) {
  this.name = name;
}
Animal.prototype.speak = function() {
  return `${this.name} makes a sound`;
};

function Dog(name, breed) {
  Animal.call(this, name);     // 继承属性
  this.breed = breed;
}
Dog.prototype = Object.create(Animal.prototype);  // 继承方法
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function() {
  return `${this.name} barks!`;
};

const d = new Dog('旺财', '金毛');
console.log(d.speak());   // "旺财 makes a sound"
console.log(d.bark());    // "旺财 barks!"
console.log(d instanceof Dog);    // true
console.log(d instanceof Animal); // true

// ES6 class 语法糖
class Animal2 {
  constructor(name) { this.name = name; }
  speak() { return `${this.name} makes a sound`; }
}
class Dog2 extends Animal2 {
  constructor(name, breed) {
    super(name);
    this.breed = breed;
  }
  bark() { return `${this.name} barks!`; }
}
// 底层仍然是原型链: Dog2.prototype → Animal2.prototype → Object.prototype → null

6.5.5 部署图 — 多运行时部署拓扑

PlantUML diagram
图 2.5.1:部署图 — Browser / Node.js / Deno / Cloudflare Workers 运行时拓扑

6.6 核心语法速查

6.6.1 数据类型速查

类型值typeof说明
Undefinedundefined"undefined"变量未赋值、函数无返回值
Nullnull"object" (bug)空对象引用,原型链终点
Booleantrue / false"boolean"Falsy: 0, "", null, undefined, NaN, false
Number42, 3.14, NaN, Infinity"number"IEEE 754 双精度, 0.1+0.2≠0.3
BigInt42n"bigint"任意精度整数
String'hello', "world", \`tpl\`"string"不可变,UTF-16 编码
SymbolSymbol('id')"symbol"唯一标识符,对象 key 用
Object{}, [], function() {}"object"/"function"引用类型,键值对集合

类型判断最佳实践

typeof 42                // "number"
typeof 'hello'           // "string"
typeof undefined         // "undefined"
typeof null              // "object" — 历史 Bug!
typeof function(){}      // "function"
typeof []                // "object"

Array.isArray([])        // true — 推荐
Object.prototype.toString.call([])    // "[object Array]"
Object.prototype.toString.call(null)  // "[object Null]"

// 判断 null
val === null

// 判断 NaN (NaN ≠ NaN)
Number.isNaN(NaN)        // true
isNaN('hello')           // true (先转数字)
Number.isNaN('hello')    // false (不转换)

6.6.2 运算符速查

类型运算符说明
算术+ - * / % **加法可做字符串拼接; ** 幂运算
比较== != === !== > < >= <==== 严格相等(类型+值); == 会类型转换
逻辑&& || ! ???? nullish: 仅 null/undefined 触发默认值
位运算& | ^ ~ << >> >>>操作 32 位有符号整数
三元c ? a : b条件表达式
可选链?.obj?.prop?.sub 遇到 null/undefined 短路返回 undefined
展开...展开数组/对象,或收集剩余参数
逗号,返回最后一个表达式的值

== vs === 典型坑

0 == false    // true
0 === false   // false
'' == false   // true
'' === false  // false
null == undefined   // true — 特殊规则
null === undefined  // false
NaN == NaN          // false — 唯一不自等的值
[1,2] == '1,2'     // true — 数组先转字符串

?? vs ||

0 || 'default'    // 'default' — 0 被当作 falsy
0 ?? 'default'    // 0 — 只有 null/undefined 触发

'' || 'default'   // 'default'
'' ?? 'default'   // ''

false || true     // true
false ?? true     // false

6.6.3 Array 方法速查

分类方法说明改原数组?
遍历 (不产生新数组)forEach(fn)遍历每个元素,无返回值否
some(fn)有一个通过即返回 true否
every(fn)全部通过才返回 true否
find(fn)返回第一个通过的元素否
findLast(fn)返回最后一个通过的元素 (ES2023)否
变换 (产生新数组)map(fn)映射每个元素到新值否
filter(fn)过滤出通过的元素否
flatMap(fn)map 后自动 flatten 一层否
归约reduce(fn, init)累积计算;reduceRight 从右开始否
增删改 (改原数组)push/pop尾部添加/移除是
unshift/shift头部添加/移除是
splice(i, n, ...)删除/插入/替换是
sort(fn)排序 (默认按字符串)是
reverse()反转是
子数组 (不修改)slice(s, e)截取子数组否
concat(arr)合并数组否
includes(v)是否包含某个值否
indexOf(v)返回首次出现索引否
ES6+ 新增flat(n)展平嵌套 n 层, Infinity 全展平否
at(i)支持负数索引: arr.at(-1) 最后一个否
with(i, v)不可变替换: 返回新数组 (ES2023)否
toReversed/Sorted/Spliced()不可变版的 reverse/sort/splice否

常用模式

const nums = [1, 2, 3, 4, 5];

nums.filter(n => n > 2).map(n => n * 2);  // [6, 8, 10]
nums.reduce((sum, n) => sum + n, 0);       // 15
nums.flatMap(n => [n, n * 2]);             // [1,2, 2,4, 3,6, 4,8, 5,10]

// 去重
[...new Set([1,1,2,2,3])];                // [1, 2, 3]

// 排序数字 (默认按字符串排序!)
[10, 2, 1].sort();                         // [1, 10, 2]  - 错!
[10, 2, 1].sort((a,b) => a - b);          // [1, 2, 10]  - 对

6.6.4 String 方法速查

方法说明示例
length字符串长度 (UTF-16 单元数)'你好'.length → 2
charAt(i) / at(i)第 i 个字符; at 支持负数'abc'.at(-1) → 'c'
includes(s)是否包含子串'hello'.includes('ll') → true
startsWith(s) / endsWith(s)判断前缀/后缀'hello'.startsWith('he') → true
indexOf(s) / lastIndexOf(s)首次/末次出现索引, -1 表示未找到'abca'.indexOf('a') → 0
slice(s, e) / substring(s, e)截取子串; slice 支持负数'abc'.slice(1) → 'bc'
toLowerCase() / toUpperCase()大小写转换
trim() / trimStart() / trimEnd()去空格/首部/尾部
replace(r, s) / replaceAll(r, s)替换首个/全部匹配'a a'.replaceAll('a','b') → 'b b'
split(sep)按分隔符拆分为数组'a,b,c'.split(',') → ['a','b','c']
padStart(n,c) / padEnd(n,c)填充到指定长度'42'.padStart(5,'0') → '00042'
match(re) / matchAll(re)正则匹配

模板字面量

const name = 'World';
const greeting = `Hello ${name}! Backtick \` and ${2 + 3}`;
// "Hello World! Backtick ` and 5"

const multiline = `第1行
第2行`;  // 保留换行

function tag(strings, ...values) {
  return strings.reduce((r,s,i) => r + s + (values[i]?.toUpperCase() || ''), '');
}
tag`hello ${'world'}`;  // "hello WORLD"

6.6.5 Object 方法速查

方法说明
Object.keys(obj)返回自身可枚举属性名数组
Object.values(obj)返回自身可枚举属性值数组
Object.entries(obj)返回 [key, value] 对数组
Object.fromEntries(arr)从 [key,value] 数组构造对象 (Map ↔ Object 互转)
Object.assign(target, ...srcs)浅拷贝: 将源对象属性复制到目标
Object.create(proto)以 proto 为原型创建新对象
Object.defineProperty(obj, key, desc)精确定义属性 (可设 getter/setter/writable/enumerable)
Object.freeze(obj)冻结对象: 不可增删改 (浅冻结)
Object.seal(obj)密封对象: 不可增删, 可修改现有属性值
Object.hasOwn(obj, prop)是否自身属性 (比 hasOwnProperty 更安全)

对象操作模式

// 浅拷贝 (3 种方式)
const copy  = { ...obj };
const copy2 = Object.assign({}, obj);
const copy3 = structuredClone(obj);  // 深拷贝! (ES2021, 支持循环引用)

// Map ↔ Object 互转
const m = new Map([['a', 1], ['b', 2]]);
const o = Object.fromEntries(m);        // { a: 1, b: 2 }
const m2 = new Map(Object.entries(o));  // Map(2) { 'a' => 1, 'b' => 2 }

// 属性描述符
Object.defineProperty(obj, 'fullName', {
  get() { return `${this.first} ${this.last}`; },
  set(v) { [this.first, this.last] = v.split(' '); },
  enumerable: true,
});

// 解构 + 默认值 + 重命名
const { name: userName = 'anonymous', age = 0 } = user;
const [first, second, ...rest] = array;

6.6.6 异步模式速查

场景写法说明
串行await a(); await b();一个完成后再做下一个
并发await Promise.all([a(), b()])同时开始,全部完成才继续
竞速await Promise.race([a(), b()])任意一个 settle 即返回
任一成功await Promise.any([a(), b()])第一个 fulfilled; 全 rejected 才报 AggregateError
全结果await Promise.allSettled([a(), b()])全部 settle 后返回 {status, value/reason}[]
超时Promise.race([req, timeout(ms)])手动创建 reject 的定时 Promise
错误重试见下方代码async/await + try/catch 循环

常用工具函数

// 延时
const sleep = ms => new Promise(r => setTimeout(r, ms));

// 超时
const withTimeout = (promise, ms) =>
  Promise.race([promise, sleep(ms).then(() => { throw new Error('Timeout'); })]);

// 重试
async function retry(fn, times = 3, delay = 1000) {
  for (let i = 0; i < times; i++) {
    try { return await fn(); }
    catch (err) {
      if (i === times - 1) throw err;
      await sleep(delay * (i + 1));  // 指数退避
    }
  }
}

// 并发限制 (最多同时 N 个)
async function concurrentLimit(tasks, limit = 3) {
  const results = [], executing = [];
  for (const task of tasks) {
    const p = Promise.resolve().then(() => task()).then(r => results.push(r));
    executing.push(p);
    if (executing.length >= limit) {
      await Promise.race(executing);
      executing.splice(executing.findIndex(e => e === p), 1);
    }
  }
  await Promise.all(executing);
  return results;
}

6.6.7 模块化速查

方式导出导入
ESM
(ES2015+)
export const x = 1;
export default fn;
export { a, b as c };
import { x } from './m.js';
import fn from './m.js';
import * as M from './m.js';
const m = await import('./m.js'); (动态导入)
CJS
(Node.js 传统)
module.exports = fn;
exports.x = 1;
const fn = require('./m');
const { x } = require('./m');

典型导出模式

// math.js — ESM 命名导出 (推荐)
export function add(a, b) { return a + b; }
export function sub(a, b) { return a - b; }
export const PI = 3.14159;

// app.js — 导入
import { add, sub, PI } from './math.js';
import * as math from './math.js';   // math.add(1, 2)

// config.js — 默认导出 (一个模块一个)
export default { apiUrl: '/api', debug: true };
import config from './config.js';

// re-export (聚合模块)
export { add, sub } from './math.js';
export { default as config } from './config.js';
export * from './math.js';            // 全部再导出

6.6.7.1 文件扩展名与模块类型判断

Node.js 通过文件扩展名和 package.json 的 "type" 字段决定 JS 文件按哪种模块系统处理:

PlantUML diagram
Node.js 模块类型判断流程图
扩展名模块类型说明
.mjsESM明确标识为 ES Module,始终使用 import/export 语法
.cjsCommonJS明确标识为 CommonJS,始终使用 require/module.exports
.js取决于 package.json若最近 package.json 有 "type": "module" → ESM;否则 → CJS(默认)

package.json 配置

// package.json — 设置项目默认模块类型
{
  "type": "module"   // .js 文件默认按 ESM 处理
}
// 或
{
  "type": "commonjs" // .js 文件默认按 CJS 处理(默认值,可省略)
}

典型互操作

// ESM 中导入 CJS (.cjs 或 .js 被识别为 CJS 的项目)
import foo from './legacy.cjs';       // 默认导入 (module.exports)
import { named } from './legacy.cjs'; // 命名导入可能不支持
import * as mod from './legacy.cjs';  // 命名空间导入 (推荐)
const mod = await import('./legacy.cjs'); // 动态导入

// CJS 中导入 ESM (.mjs 或 .js 被识别为 ESM 的项目)
// 只能用动态 import(),不能用 require()
const esmModule = await import('./esm-module.mjs');
// const esm = require('./esm-module.mjs');  // ❌ 报错: ERR_REQUIRE_ESM

6.6.8 常见坑 & 最佳实践

坑原因正确做法
0.1 + 0.2 !== 0.3 IEEE 754 浮点误差 Math.abs(a - b) < Number.EPSILON
或用整数运算: (1+2)/10
typeof null === 'object' 历史 Bug,无法修复 val === null 直接判断
NaN !== NaN IEEE 754 规定 NaN 不自等 Number.isNaN(val)
this 指向丢失 函数内 this 取决于调用方式 箭头函数捕获外层 this; 或 .bind(this)
for in 遍历原型属性 for-in 会枚举原型链上可枚举属性 for (const k of Object.keys(obj))
或加 hasOwnProperty 判断
sort() 按字符串排序 默认比较的是字符串形式 [10,2,1].sort((a,b) => a - b)
new Array(n) 稀疏数组 只有 length,没有元素 Array.from({ length: n }, (_,i) => i)
var 变量提升 & 函数作用域 var 没有块级作用域 始终用 const (不变) 或 let (可变)
parseInt 不带基数 parseInt('08') 在老引擎当作八进制 parseInt('08', 10)
数组/对象是引用 const a = [1]; const b = a; 共享引用 浅拷贝: [...a]/{...obj}; 深拷贝: structuredClone

现代最佳实践清单

// 1. 始终用 const/let,永远不用 var
// 2. === 代替 ==,除非明确需要类型转换
// 3. 使用可选链 ?. 和空值合并 ?? 安全访问
// 4. 箭头函数用于回调; 普通函数/full function 声明用于方法
// 5. 异步操作用 async/await,避免回调地狱
// 6. try/catch 包裹 await,不要让 unhandled rejection 逃逸
// 7. 解构赋值提高可读性
// 8. 模板字面量代替 + 拼接
// 9. for...of 遍历数组,for...in 只用于对象(且加 hasOwnProperty)
// 10. 启用严格模式: 文件开头 'use strict'; 或 ESM 自动严格

6.7 前端开发速查

6.7.1 DOM 操作速查

操作API
按 ID 获取document.getElementById('hcj-id')
按选择器获取document.querySelector('.class') / querySelectorAll
创建元素document.createElement('div')
创建文本document.createTextNode('text')
插入子节点parent.appendChild(child) / parent.insertBefore(new, ref)
移除节点el.remove() / parent.removeChild(child)
替换节点parent.replaceChild(new, old)
克隆节点el.cloneNode(true) (true=深拷贝)
获取/设置 HTMLel.innerHTML / el.outerHTML
获取/设置文本el.textContent / el.innerText
属性操作el.getAttribute('href') / setAttribute / removeAttribute
data-* 属性el.dataset.key
class 操作el.classList.add/remove/toggle/contains('cls')
样式操作el.style.color = 'red'; 读取计算样式: getComputedStyle(el).color

DOM 遍历

el.parentElement        // 父元素
el.children             // 子元素 HTMLCollection
el.firstElementChild    // 第一个子元素
el.lastElementChild
el.previousElementSibling / nextElementSibling
el.closest('.container') // 向上查找最近的匹配祖先

高效的 DOM 批量更新

// Fragment — 减少回流
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
  const li = document.createElement('li');
  li.textContent = `Item ${i}`;
  fragment.appendChild(li);
}
ul.appendChild(fragment);  // 一次性插入,只触发一次回流

6.7.2 事件处理速查

操作API
绑定事件el.addEventListener('click', handler)
解绑事件el.removeEventListener('click', handler)
一次性绑定el.addEventListener('click', fn, { once: true })
被动监听 (提升滚动性能)el.addEventListener('scroll', fn, { passive: true })
阻止默认行为event.preventDefault()
阻止冒泡event.stopPropagation()
事件委托在父元素监听,通过 event.target 判断来源
自定义事件el.dispatchEvent(new CustomEvent('name', { detail: {...} }))

常用事件类型

// 鼠标: click, dblclick, mousedown, mouseup, mousemove, mouseenter, mouseleave
// 键盘: keydown, keyup, keypress (已废弃)
// 表单: submit, change, input, focus, blur
// 文档: DOMContentLoaded, load, beforeunload
// 视口: scroll, resize
// 触摸: touchstart, touchmove, touchend

事件委托模式

// 在父容器监听,避免给每个子元素都绑事件
document.getElementById('list').addEventListener('click', (e) => {
  if (e.target.matches('.item')) {
    console.log('clicked:', e.target.textContent);
  }
  // 更精确: e.target.closest('.item')
});

6.7.3 Fetch / 网络请求速查

操作代码
GETfetch('/api/data').then(r => r.json())
POST JSONfetch('/api/data', { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(data) })
POST FormDatafetch('/upload', { method:'POST', body: new FormData(form) })
超时控制Promise.race([fetch(url), timeout(5000)])
中止请求const ctrl = new AbortController(); fetch(url, { signal: ctrl.signal }); ctrl.abort();
流式读取const reader = response.body.getReader();

async/await + 错误处理

async function getData(url) {
  try {
    const res = await fetch(url);
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    return await res.json();
  } catch (err) {
    console.error('fetch failed:', err);
    throw err;
  }
}

请求拦截器模式

async function fetchWithRetry(url, options = {}, retries = 3) {
  for (let i = 0; i < retries; i++) {
    try {
      const res = await fetch(url, options);
      if (res.ok) return await res.json();
      if (res.status >= 500 && i < retries - 1) continue; // 服务端错误重试
      throw new Error(`HTTP ${res.status}`);
    } catch (err) {
      if (i === retries - 1) throw err;
      await new Promise(r => setTimeout(r, 1000 * (i + 1)));
    }
  }
}

6.7.4 浏览器存储速查

存储类型容量持久性作用域API
localStorage~5MB永久保留同源getItem/setItem/removeItem/clear
sessionStorage~5MB标签页关闭即清同源同标签同上
cookie~4KB可设过期时间同源+路径document.cookie
IndexedDB> 50MB永久保留同源异步事务式 NoSQL
Cache API弹性永久保留同源Service Worker 缓存
// localStorage (同步,只能存字符串)
localStorage.setItem('key', JSON.stringify(obj));
const data = JSON.parse(localStorage.getItem('key'));
localStorage.removeItem('key');

// sessionStorage API 相同
sessionStorage.setItem('token', 'abc123');

// cookie 操作
document.cookie = 'name=value; max-age=3600; path=/; SameSite=Lax';
// 读取: document.cookie 返回所有 cookie 字符串

// IndexedDB 简单封装
function openDB(name, version = 1) {
  return new Promise((resolve, reject) => {
    const req = indexedDB.open(name, version);
    req.onsuccess = () => resolve(req.result);
    req.onerror = () => reject(req.error);
    req.onupgradeneeded = (e) => {
      e.target.result.createObjectStore('store', { keyPath: 'id' });
    };
  });
}

6.7.5 定时器 & 动画速查

API说明最小间隔
setTimeout(fn, ms)延迟执行一次~4ms (嵌套 >5 层)
setInterval(fn, ms)重复执行~4ms
clearTimeout/clearInterval取消定时器
requestAnimationFrame(fn)下一帧渲染前执行~16.7ms (60fps)
requestIdleCallback(fn)浏览器空闲时执行不固定
setImmediate(fn)Node.js 专用,下一个 tick

raf 动画循环

function animate() {
  // 更新动画状态
  el.style.transform = `translateX(${x}px)`;
  x += 2;
  if (x < 300) requestAnimationFrame(animate);
}
requestAnimationFrame(animate);

定时器 + setInterval 的坑

// 坏: 如果 fn 执行时间 > 间隔,会堆积回调
setInterval(fn, 100);

// 好: 用 setTimeout 递归模拟精确间隔
function safeInterval(fn, ms) {
  let stopped = false;
  async function loop() {
    if (stopped) return;
    const start = Date.now();
    await fn();
    const elapsed = Date.now() - start;
    setTimeout(loop, Math.max(0, ms - elapsed));
  }
  setTimeout(loop, ms);
  return () => { stopped = true; };  // 返回停止函数
}

6.7.6 BOM (浏览器对象模型) 速查

对象常用属性/方法说明
windowinnerWidth / innerHeight视口尺寸 (含滚动条)
scrollX / scrollY页面滚动偏移
scrollTo(x, y) / scrollBy(dx, dy)滚动; 平滑: { behavior: 'smooth' }
open(url) / close()打开/关闭窗口 (受弹窗拦截器限制)
navigatoruserAgentUA 字符串 (不可靠,推荐 feature detection)
language / languages浏览器语言偏好
onLine是否联网 (不可靠)
clipboard.writeText(s)写入剪贴板 (需用户手势或 HTTPS)
screenwidth / height屏幕分辨率
availWidth / availHeight可用区域 (减去任务栏)
locationhref完整 URL; 赋值即跳转
protocol / host / hostname / port / pathname / search / hashURL 各组成部分
reload()刷新页面
replace(url)替换当前历史记录 (不可后退)
assign(url)跳转 (可后退)
historyback() / forward() / go(n)历史导航
pushState(state, '', url)新增一条历史记录 (SPA 路由核心)
replaceState(state, '', url)替换当前历史记录
window.onpopstate前进/后退时触发
弹窗alert('msg')同步阻塞,不推荐生产使用
confirm('msg')返回 true/false
prompt('msg', 'default')返回输入字符串或 null

SPA 路由基础

// 切换到 /page2,不刷新页面
history.pushState({ page: 2 }, '', '/page2');

// 监听浏览器前进/后退
window.addEventListener('popstate', (e) => {
  console.log('location changed to:', location.pathname);
  console.log('state:', e.state);  // { page: 2 }
});

// URL 解析
const url = new URL('https://example.com/page?a=1&b=2#section');
url.searchParams.get('a');          // '1'
url.searchParams.set('c', '3');     // 修改参数
url.searchParams.toString();        // 'a=1&b=2&c=3'
url.hash;                           // '#section'

6.8 TC39 提案流程

五阶段流程(Stage 0-4)

Stage名称含义
0Strawman想法阶段,可以提交到 TC39 讨论
1Proposal正式提案,需要 champion 推动,描述问题和解决方案
2Draft初稿,有精确语法和语义的规范文本
3Candidate候选,规范完成,等待浏览器实现和反馈
4Finished完成,通过 Test262 测试,正式成为 ECMAScript 标准

6.9 参考资源


第六部分:TypeScript 系统

TypeScript 是微软开发的静态类型、强类型编程语言,构建于 JavaScript 之上,由 Anders Hejlsberg(C# 之父)主导设计,2012 年首次发布。

TypeScript 是 JavaScript 的超集:所有合法的 JS 代码都是合法的 TS 代码。TypeScript 编译器将 TS 代码编译为纯 JavaScript,且类型注解在编译后被完全擦除,不影响运行时行为。其类型系统为结构化类型系统(Structural Type System),即"鸭子类型"。

TypeScript 存在的根本原因:动态类型语言在项目规模增长后,代码几乎不可读——函数的参数类型、返回值类型、对象结构全部隐式靠脑子记,维护成本指数级上升。TypeScript 的出现就是为了解决这个问题:用一套静态类型系统覆盖 JavaScript 生态,让编译器在代码运行之前就发现类型错误,同时通过类型推断保持灵活性,不必像传统静态语言那样写大量显式类型声明。本质上,TypeScript 证明了"动态类型不适合严肃工程"。
核心特征:可选静态类型 · 类型推断 · 结构化类型 · 泛型 · 声明文件(.d.ts) · 渐进式迁移 · 编译时类型擦除 · 丰富的编辑器工具链
层面核心内容
词法/语法JS 超集语法 + 类型注解(: Type)、接口、泛型、枚举、装饰器、JSX
类型系统结构化类型、类型推断、控制流分析、类型窄化、泛型、条件类型、映射类型、模板字面量类型
编译管线Scanner → Parser → Binder → Checker → Emitter(类型擦除 + downleveling)
语言服务tsserver:代码补全、错误诊断、快速修复、重构、导航;Language Service API
配置系统tsconfig.json:target、module、strict、paths、project references 等 100+ 编译选项
生态DefinitelyTyped(@types/*)、tsc CLI、ts-node、Babel/Webpack 集成、ESLint 规则

7.1 概述

TypeScript 的设计目标(来自官方 wiki)包括:

TypeScript 7.0 于 2026 年发布,核心编译器正逐步迁移至 Go 语言实现(typescript-go)以实现原生性能。当前 npm 版本(typescript 包)仍为 TypeScript 实现。

语言定位:TypeScript = JavaScript + 静态类型系统。它不是一门独立的新语言,而是 JavaScript 的类型增强层。编译结果去掉类型注解即为纯 JavaScript,无需运行时环境。

7.2 行为图

7.2.1 用例图 — 使用场景

PlantUML diagram
图 1:TypeScript 系统用例图

7.2.2 活动图 — 编译流程

PlantUML diagram
图 2:TypeScript 编译管线活动图

7.2.3 时序图 — 编辑器交互(tsserver)

PlantUML diagram
图 3:IDE / 编辑器通过 tsserver 获取语言服务

7.2.4 时序图 — 类型检查与推断

PlantUML diagram
图 4:Checker 类型检查与推断核心流程

7.3 结构图

7.3.1 组件图 — 编译器架构

PlantUML diagram
图 5:TypeScript 编译器整体架构组件图

7.3.2 包图 — 核心模块组织

PlantUML diagram
图 6:TypeScript 编译器核心模块依赖关系

7.3.3 类图 — 类型系统层次

PlantUML diagram
图 7:TypeScript 类型系统层次类图

7.4 使用手册

7.4.1 安装与配置

# 全局安装编译器
npm install -g typescript

# 项目本地安装
npm install -D typescript

# 初始化 tsconfig.json
tsc --init

# 编译
tsc                    # 使用 tsconfig.json
tsc src/index.ts      # 编译单个文件
tsc --watch           # 监视模式

核心编译选项:

{
  "compilerOptions": {
    "target": "ES2022",        // 输出 JS 版本
    "module": "NodeNext",      // 模块系统
    "strict": true,            // 严格模式(推荐)
    "outDir": "./dist",        // 输出目录
    "rootDir": "./src",        // 源码根目录
    "declaration": true,       // 生成 .d.ts
    "sourceMap": true,         // 生成 source map
    "esModuleInterop": true,   // CJS/ESM 互操作
    "skipLibCheck": true       // 跳过声明文件类型检查
  }
}

7.4.2 基础类型

// 基本类型注解
let isDone: boolean = false;
let count: number = 42;
let name: string = "TypeScript";
let list: number[] = [1, 2, 3];
let tuple: [string, number] = ["hello", 10];

// 联合类型
type Status = "idle" | "loading" | "success" | "error";

// 枚举
enum Color { Red, Green, Blue }
enum Direction { Up = "UP", Down = "DOWN" }

// 特殊类型
let notSure: unknown = 4;   // 安全版的 any
let unusable: void = undefined;
function error(): never { throw new Error(); }

// 类型推断(自动推导类型)
let msg = "hello";  // msg: string

7.4.3 接口与类型别名

// Interface(首选,支持声明合并)
interface User {
  readonly id: number;
  name: string;
  email?: string;           // 可选属性
  [key: string]: unknown;   // 索引签名
}

// Type Alias(需要联合/交叉/元组等特性时使用)
type Point = { x: number; y: number };
type ID = string | number;
type PartialUser = Partial<User>;

// 接口继承
interface Admin extends User {
  role: "admin" | "superadmin";
  permissions: string[];
}

// 交叉类型
type ColoredPoint = Point & { color: string };

7.4.4 函数类型

// 函数声明
function add(a: number, b: number): number {
  return a + b;
}

// 函数类型表达式
type MathFn = (x: number, y: number) => number;
const multiply: MathFn = (x, y) => x * y;

// 可选参数与默认值
function greet(name: string, greeting?: string): string {
  return `${greeting || "Hello"}, ${name}`;
}

// 剩余参数
function sum(...nums: number[]): number {
  return nums.reduce((a, b) => a + b, 0);
}

// 函数重载
function makeDate(timestamp: number): Date;
function makeDate(y: number, m: number, d: number): Date;
function makeDate(yOrTimestamp: number, m?: number, d?: number): Date {
  if (m !== undefined && d !== undefined) {
    return new Date(yOrTimestamp, m, d);
  }
  return new Date(yOrTimestamp);
}

// 泛型函数
function firstElement<T>(arr: T[]): T | undefined {
  return arr[0];
}

7.4.5 泛型

// 泛型接口
interface Repository<T> {
  getById(id: string): T;
  getAll(): T[];
  create(item: Omit<T, "id">): T;
}

// 泛型约束(extends)
interface HasLength { length: number; }
function logLength<T extends HasLength>(arg: T): T {
  console.log(arg.length);
  return arg;
}

// keyof + 泛型约束
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

// 泛型默认值
function create<T = string>(value?: T): T[] {
  return value ? [value] : [];
}

// 条件类型 + infer
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type UnpackPromise<T> = T extends Promise<infer U> ? U : T;

7.4.6 高级类型操作

// 映射类型
type Readonly<T> = { readonly [P in keyof T]: T[P] };
type Optional<T> = { [P in keyof T]?: T[P] };

// 条件类型
type IsString<T> = T extends string ? true : false;
type NonNullable<T> = T extends null | undefined ? never : T;

// 模板字面量类型
type EventName<T extends string> = `on${Capitalize<T>}`;
type MouseEvents = EventName<"click" | "hover" | "drag">;
// "onClick" | "onHover" | "onDrag"

// 常用内置 Utility Types
type Todo = { title: string; desc: string; done: boolean };

type PartialTodo = Partial<Todo>;        // 全部可选
type RequiredTodo = Required<Todo>;      // 全部必填
type ReadonlyTodo = Readonly<Todo>;      // 全部只读
type Picked = Pick<Todo, "title" | "done">;  // 选取
type Omitted = Omit<Todo, "desc">;       // 排除
type FromUnion = Extract<"a"|"b"|"c", "a"|"c">;  // "a"|"c"
type Excluded = Exclude<"a"|"b"|"c", "a">;        // "b"|"c"
type Params = Parameters<typeof add>;     // 参数元组类型

7.4.7 模块系统

// ES Module(推荐)
export interface Config { debug: boolean }
export const VERSION = "1.0";
export default class App {}

import App, { Config, VERSION } from "./app";

// CommonJS 互操作
import fs = require("fs");       // CJS 导入
export = myLib;                  // CJS 导出

// 动态导入(返回 Promise)
const module = await import("./dynamic-module");

// 类型导入(仅编译时,不产生运行时导入)
import type { User } from "./types";
import { type User, createUser } from "./user";

7.4.8 声明文件

// .d.ts — 为 JS 库提供类型描述
declare module "my-lib" {
  export function doSomething(x: number): string;
  export const version: string;
}

// 全局声明
declare const APP_VERSION: string;
declare function initialize(config: Config): void;

// 扩展已有模块的类型(Declaration Merging)
interface String {
  customMethod(): void;
}

// 扩展第三方模块
declare module "express" {
  interface Request {
    user?: User;
  }
}

7.4.9 tsconfig 核心配置

{
  "compilerOptions": {
    /* 严格模式(推荐全部开启) */
    "strict": true,              // 开启所有严格检查
    "noImplicitAny": true,       // 禁止隐式 any
    "strictNullChecks": true,    // null/undefined 严格检查
    "strictFunctionTypes": true, // 函数类型严格逆变
    "noUnusedLocals": true,      // 禁止未使用的局部变量
    "noUnusedParameters": true,  // 禁止未使用的参数
    "exactOptionalPropertyTypes": true, // 精确可选属性

    /* 模块解析 */
    "moduleResolution": "bundler", // bundler/node16/nodenext
    "baseUrl": ".",              // 非相对模块导入基准
    "paths": { "@/*": ["./src/*"] }, // 路径别名

    /* 输出控制 */
    "noEmit": true,              // 仅类型检查,不产生输出
    "noEmitOnError": true,       // 类型错误时不产生输出
    "declarationMap": true,      // 声明文件 source map

    /* 其他 */
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,   // 允许导入 JSON
    "isolatedModules": true      // 确保单文件编译安全
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"],
  "references": [                // 项目引用(monorepo)
    { "path": "./packages/core" },
    { "path": "./packages/utils" }
  ]
}

7.4.10 工具链

工具作用
tscTypeScript 官方编译器
ts-node直接执行 TypeScript 文件(开发用)
tsx更快的 ts-node 替代(基于 esbuild)
tsup基于 esbuild 的 TS 打包工具
vite内置 TS 支持的构建工具
esbuild / swc高速 TS 转译(不做类型检查)
ts-jest / vitest支持 TS 的测试框架
tsc --noEmit仅类型检查(CI 使用)
dts-bundle-generator合并 .d.ts 声明文件

Vue.js 生态系统系统拆解

Vue.js 是一个渐进式 JavaScript 框架,用于构建用户界面。Vue 3 由三大核心子系统组成:响应式系统(基于 Proxy 的依赖追踪)、编译器(模板→渲染函数)和渲染器(VDOM diff/patch)。生态包含 Vue Router(路由)、Pinia(状态管理)、Vite(构建工具)等。

核心设计理念:编译器感知的虚拟 DOM
Vue 同时对模板进行编译时静态分析 和 运行时 VDOM 更新。编译器留下优化提示(patch flags、静态提升、树展平),运行时据此走快速路径跳过静态内容。这使得 Vue 在保持声明式模板开发体验的同时,获得了接近手写 vanilla JS 的性能。

1. 系统概览

1.1 渲染管线总览

模板 → 编译 → 渲染函数 → VNode 树 → 挂载 → 真实 DOM
                       ↑                           │
                       └── 补丁(diff + 更新) ←── 响应式效应触发

1.2 技术栈一览

子系统仓库核心职责
Vue Corevuejs/core响应式、编译器、渲染器、组件系统
Vue Routervuejs/router客户端路由,URL ↔ 组件映射
Piniavuejs/pinia全局状态管理(替代 Vuex)
Vitevitejs/vite开发服务器 + 生产构建(Rolldown + Oxc)

2. 行为图

先梳理 Vue.js 系统"做什么"——从整体用例到关键动态流程。

2.1 用例图

从开发者视角描述 Vue.js 生态提供的核心功能:创建应用、构建组件、配置路由、管理状态、构建部署。

PlantUML diagram
图 2.1:Vue.js 生态系统用例图

2.2 活动图 — 渲染管线

描述从写模板到浏览器 DOM 更新的完整渲染流程。带泳道区分编译时和运行时两个阶段。

PlantUML diagram
图 2.2:Vue 渲染管线活动图(编译时 vs 运行时)

2.3 状态机图 — 组件生命周期

Vue 组件从创建、挂载、更新到卸载的完整生命周期状态迁移。

PlantUML diagram
图 2.3:Vue 组件生命周期状态机

2.4 序列图 — 响应式更新链路

当开发者修改 ref 值后,从依赖追踪到 VDOM diff 再到 DOM 更新的完整时序。

PlantUML diagram
图 2.4:响应式状态变更 → DOM 更新序列图

3. 结构图

从宏观架构到内部细节,逐步拆解 Vue.js 系统的静态组成。

3.1 组件图 — 生态全景

展示 Vue.js 生态系统各组件之间的依赖和接口关系。

PlantUML diagram
图 3.1:Vue.js 生态系统组件图

3.2 包图 — 核心包结构

Vue 3 core 内部的模块划分及依赖关系。

PlantUML diagram
图 3.2:Vue 3 Core 包图

3.3 类图 — 核心 API

响应式系统、组件系统和路由系统的核心 API 及类型关系。

PlantUML diagram
图 3.3:Vue 核心 API 类图(响应式 / 组件 / 路由)

3.4 部署图

Vue 应用的开发、构建和上线部署拓扑。

PlantUML diagram
图 3.4:Vue 应用开发 → 生产部署拓扑

4. 使用手册

阅读建议:4.1 快速起步 → 4.2 核心开发模式 → 按需查阅 4.3~4.7 场景实战。下方代码均基于 <script setup lang="ts">(组合式 API + TypeScript),是当前 Vue 3 推荐的主流写法。

4.1 快速起步:从零到第一个页面

项目初始化

npm create vite@latest my-app -- --template vue-ts
cd my-app
npm install
npm run dev          # 打开 http://localhost:5173

生成的标准目录结构:

my-app/
├── index.html              # Vite 入口(根 div#app)
├── vite.config.ts           # Vite 配置
├── tsconfig.json
├── package.json
└── src/
    ├── main.ts              # 应用入口:createApp(...).mount('#app')
    ├── App.vue              # 根组件
    ├── components/          # 公共组件
    ├── views/               # 页面级组件
    ├── router/              # 路由配置(手动添加)
    ├── stores/              # Pinia store(手动添加)
    ├── api/                 # HTTP 请求封装(手动添加)
    └── assets/              # 静态资源(图片、CSS)

入口文件 src/main.ts

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
import './assets/main.css'

const app = createApp(App)
app.use(createPinia())
app.use(router)
app.mount('#app')

index.html 唯一要改的地方

<body>
  <div id="app"></div>
  <script type="module" src="/src/main.ts"></script>
</body>

4.2 核心开发模式

4.2.1 ref vs reactive:什么时候用什么

场景用原因
基础类型(string / number / boolean)ref()reactive 不支持原始值
对象/数组(本地状态)ref()可整体替换,解构不丢失响应性
表单对象(字段多且固定结构)reactive()模板中免 .value,读写像普通对象
推荐默认用 ref — 心智负担小、解构安全(配合 toRefs)、可整体替换、不存在 reactive 的限制(不可替换、不可解构丢失响应性)。仅在明确需要模板中省略 .value 时使用 reactive。
// ref:模板/script 里统一 .value 访问(模板中自动解包顶层 ref)
const count = ref(0)
const user = ref({ name: 'Brian', role: 'CEO' })

// 读取:count.value  修改:count.value++
// 替换整个对象:user.value = newUser      ✅
// 解构后仍响应:const { name } = toRefs(user.value)   ✅

// reactive:模板中直接用,但限制多
const form = reactive({ username: '', password: '' })
// 不可替换:form = newForm  ❌(断开响应性)
// 不可解构:const { username } = form  ❌(失去响应性)

4.2.2 computed:派生状态自动推导

const items = ref([{ price: 10, qty: 2 }, { price: 30, qty: 1 }])

const total = computed(() =>
  items.value.reduce((sum, i) => sum + i.price * i.qty, 0)
)
// total 是只读 ref,依赖变化时自动重算、有缓存

4.2.3 watch vs watchEffect:何时用哪个

// watch:需要"前后值对比"时用
watch(count, (newVal, oldVal) => {
  if (newVal > 100) alert('超出上限')
})

// watchEffect:不在乎旧值,自动追踪依赖
watchEffect(() => {
  localStorage.setItem('count', String(count.value))
})
// 自动追踪 count.value,立即执行一次,依赖变自动重跑

4.2.4 v-model 双向绑定

<!-- 等价于 :value="text" @input="text = $event.target.value" -->
<input v-model="text" />

<!-- 修饰符:.lazy(change 触发).number(转数字).trim -->
<input v-model.lazy.trim="text" />

4.2.5 v-if vs v-show

v-ifv-show
机制DOM 创建/销毁CSS display:none 切换
初始开销低(不渲染)高(总是渲染)
切换开销高(重建 DOM)低(仅改样式)
适用条件很少变化频繁切换(tab、弹窗)

4.2.6 v-for 必须带 key

<!-- bad: 没有 key,diff 可能错位 -->
<li v-for="item in list">{{ item.name }}</li>

<!-- good: 用唯一 id 做 key,不用 index -->
<li v-for="item in list" :key="item.id">{{ item.name }}</li>

4.3 组件实战:父子通信

场景:父组件 → 子组件(Props)

// ── 子组件 ChildCard.vue ──
<script setup lang="ts">
interface Props {
  title: string
  count?: number          // ? = 可选
}
const props = withDefaults(defineProps<Props>(), {
  count: 0                // 默认值
})
</script>

<template>
  <div class="card">
    <h3>{{ props.title }}</h3>
    <span>{{ props.count }}</span>
  </div>
</template>

// ── 父组件 ──
<ChildCard title="总览" :count="total" />

场景:子组件 → 父组件(Emits)

// ── 子组件 ConfirmDialog.vue ──
<script setup lang="ts">
const emit = defineEmits<{
  confirm: [id: number]
  cancel: []
}>()
</script>

<template>
  <button @click="emit('confirm', 42)">确定</button>
  <button @click="emit('cancel')">取消</button>
</template>

// ── 父组件 ──
<ConfirmDialog @confirm="handleConfirm" @cancel="show = false" />

场景:插槽(Slot)—— 父向子传递模板内容

// ── 子组件 Layout.vue ──
<template>
  <header>
    <slot name="header" />
  </header>
  <main>
    <slot />               <!-- 默认插槽 -->
  </main>
</template>

// ── 父组件 ──
<Layout>
  <template #header>
    <h1>页面标题</h1>
  </template>
  <p>这是主体内容</p>
</Layout>

场景:跨层级传递(provide / inject)

// ── 祖先组件 ──
import { provide, ref } from 'vue'
const theme = ref('light')
provide('theme', theme)                  // 也可 provide readonly(theme)

// ── 深层子组件 ──
import { inject } from 'vue'
const theme = inject('theme')            // 类型自动推导

4.4 路由实战

典型项目结构

src/router/index.ts   # 路由配置
src/views/            # 页面组件(LoginView.vue, HomeView.vue)
src/layouts/          # 布局组件(含 <RouterView />)

路由配置(含懒加载 + 鉴权 + 404)

import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: '/',
      component: () => import('@/layouts/DefaultLayout.vue'),
      children: [
        { path: '', name: 'home', component: () => import('@/views/HomeView.vue') },
        {
          path: 'admin',
          meta: { requiresAuth: true, role: 'admin' },
          component: () => import('@/views/AdminView.vue'),
        },
      ],
    },
    { path: '/login', component: () => import('@/views/LoginView.vue') },
    { path: '/:pathMatch(.*)*', redirect: '/' },  // 404 fallback
  ],
})

// 全局前置守卫:鉴权
router.beforeEach((to) => {
  const token = localStorage.getItem('token')
  if (to.meta.requiresAuth && !token) return '/login'
})

页面中获取路由参数、跳转

import { useRoute, useRouter } from 'vue-router'

const route = useRoute()
const router = useRouter()

// 读当前路由
console.log(route.path)           // /user/123
console.log(route.params.id)      // 123    (path: '/user/:id')
console.log(route.query.search)   // 'vue'  (?search=vue)

// 跳转
router.push({ name: 'home' })
router.push({ path: '/user/42', query: { tab: 'profile' } })
router.replace('/login')                         // 替换历史记录
router.go(-1)                                    // 后退

4.5 状态管理实战(Pinia)

组合式风格(推荐,写法与组件 setup 一致)

// stores/user.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { apiLogin, apiGetUserInfo } from '@/api/user'

export const useUserStore = defineStore('user', () => {
  const token = ref(localStorage.getItem('token') || '')
  const userInfo = ref(null)
  const isLoggedIn = computed(() => !!token.value)

  async function login(username: string, password: string) {
    const res = await apiLogin({ username, password })
    token.value = res.token
    localStorage.setItem('token', res.token)
  }

  async function fetchUserInfo() {
    userInfo.value = await apiGetUserInfo()
  }

  function logout() {
    token.value = ''
    userInfo.value = null
    localStorage.removeItem('token')
  }

  return { token, userInfo, isLoggedIn, login, fetchUserInfo, logout }
})

组件中使用

import { useUserStore } from '@/stores/user'

const userStore = useUserStore()

// 读状态
console.log(userStore.isLoggedIn)     // computed,模板中直接 {{ userStore.isLoggedIn }}
// 调 action
await userStore.login('brian', 'xxx')
// 批量修改
userStore.$patch({ token: 'newtoken' })
Pinia 对比 Vuex:无 mutations(直接改 state)、无模块嵌套(按文件平铺)、完整 TypeScript 推断、支持 store 间互相引用。从 Vuex 迁移时,最大的概念变化是去掉 mutation 层。

4.6 逻辑复用:Composable 函数

Composable 是 Vue 3 的代码复用核心机制——把有状态逻辑封装成可复用的函数。组件间共享逻辑,不再是 mixin 的"来源不明",而是显式 import。

示例:封装鼠标位置追踪

// composables/useMouse.ts
import { ref, onMounted, onUnmounted } from 'vue'

export function useMouse() {
  const x = ref(0)
  const y = ref(0)

  function update(e: MouseEvent) {
    x.value = e.pageX
    y.value = e.pageY
  }

  onMounted(() => window.addEventListener('mousemove', update))
  onUnmounted(() => window.removeEventListener('mousemove', update))

  return { x, y }
}

// 组件中使用
const { x, y } = useMouse()

示例:封装异步数据请求

// composables/useFetch.ts
import { ref, watchEffect } from 'vue'

export function useFetch<T>(url: string) {
  const data = ref<T | null>(null)
  const loading = ref(false)
  const error = ref<string | null>(null)

  async function execute() {
    loading.value = true; error.value = null
    try {
      data.value = await fetch(url).then(r => r.json())
    } catch (e) {
      error.value = (e as Error).message
    } finally {
      loading.value = false
    }
  }

  execute()
  return { data, loading, error }
}

// 组件中使用
const { data: users, loading, error } = useFetch<User[]>('/api/users')

4.7 常见踩坑与最佳实践

坑 1:reactive 整体替换会断开响应性

const state = reactive({ count: 0 })
state = { count: 1 }   // ❌ 断开了!

// ✅ 方案一:用 ref 替代
const state = ref({ count: 0 })
state.value = { count: 1 }

// ✅ 方案二:用 Object.assign 就地更新
Object.assign(state, { count: 1 })

坑 2:reactive 解构后丢失响应性

const state = reactive({ a: 1, b: 2 })
const { a, b } = state    // ❌ a, b 失去响应

// ✅ 用 toRefs
const { a, b } = toRefs(state)  // a, b 现在是 Ref<number>

坑 3:模板 ref 在 v-if 内部获取不到

// ❌ v-if 为 false 时 ref 是 null
<input v-if="show" ref="inputRef" />

// ✅ 用 v-show(DOM 始终存在但隐藏)
<input v-show="show" ref="inputRef" />

// ✅ 或用 nextTick 在显示后访问
show.value = true
await nextTick()
inputRef.value?.focus()

坑 4:watch 默认不深度监听

const obj = ref({ a: { b: 1 } })

// ❌ 浅监听:obj.a.b = 2 不会触发
watch(obj, callback)

// ✅ 加 { deep: true }
watch(obj, callback, { deep: true })

// ✅ 或用 computed 返回具体值来精确监听
watch(() => obj.value.a.b, callback)

坑 5:异步组件加载状态

import { defineAsyncComponent } from 'vue'

const HeavyChart = defineAsyncComponent({
  loader: () => import('./HeavyChart.vue'),
  loadingComponent: LoadingSpinner,   // 加载中显示
  errorComponent: ErrorFallback,      // 加载失败显示
  delay: 200,                         // 延迟 200ms 才显示 loading
  timeout: 10000,                     // 10s 超时
})

最佳实践速查

Vite 常用配置速查

// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  resolve: { alias: { '@': '/src' } },
  server: { port: 3000, proxy: { '/api': 'http://localhost:8080' } },
  build: { outDir: 'dist', cssCodeSplit: true },
})

Vite 系统架构梳理

来源: vitejs.dev · github.com/vitejs/vite · 版本: v8.1.5 · 采用 Rust 统一工具链 (Oxc + Rolldown)

1. 系统概述

Vite(法语 "快",发音 /viːt/)是一个下一代前端构建工具,由两大部分组成:

Vite 的核心理念:开发时用原生 ESM 按需编译,生产时用 Rust 工具链打包优化。传统 bundler 需要先打包整个应用才能启动 dev server,Vite 则将依赖预打包后在浏览器中按需加载源码。

关键设计决策
Vite v6+ 采用了统一的 Rust 工具链:Oxc(解析/转换/压缩)+ Rolldown(打包),替代了原先的 esbuild(dev 编译)+ Rollup(生产打包)双管线架构,消除了一致性问题并大幅提升性能。

第一部分:行为图(系统"做什么")

2.1 用例图 — 功能全景

Vite 面向三类角色的功能全景:开发者(日常开发)、插件作者(扩展系统)、框架作者(基于 Vite 构建应用框架)。

图 2.1-1: Vite 用例图

PlantUML diagram

2.2 活动图 — Dev Server 启动流程

Vite 开发服务器启动的核心路径:扫描依赖 → 预打包 → 启动 HTTP + WebSocket → 就绪。整个流程通常在 1 秒内完成。

图 2.2-1: Dev Server 冷启动活动图

PlantUML diagram

2.3 活动图 — 生产构建流程

Vite 的生产构建使用 Rolldown 作为打包引擎,Oxc 负责解析/转换/压缩,输出针对现代浏览器的优化产物。

图 2.3-1: 生产构建活动图

PlantUML diagram

2.4 时序图 — 生产构建全流程

vite build 命令的执行是一个多阶段流水线:Rolldown 解析入口 → Oxc 逐模块转换 → Tree Shaking → Code Splitting → 压缩输出。展示完整的构建链:

图 2.4-1: 生产构建时序图

PlantUML diagram

2.5 时序图 — HMR 热更新流程

HMR 是 Vite 开发体验的核心。当文件变更时,Vite 仅需 一次 HTTP 请求即可完成模块热替换,无需整页刷新。

图 2.4-1: HMR 热模块替换时序图

PlantUML diagram

2.6 时序图 — Dev Server 请求处理管线

开发服务器的请求处理管线展示了 Vite 如何将源码(JSX/TS/CSS)转换为浏览器可执行的 ES 模块。

图 2.5-1: Dev Server 源码请求处理时序图

PlantUML diagram

2.7 状态机图 — Dev Server 生命周期

图 2.6-1: Dev Server 生命周期状态机

PlantUML diagram

第二部分:结构图(系统"由什么组成")

3.1 组件图 — 核心架构

Vite 核心由多个相互协作的子系统组成。开发模式下,所有源码通过 Plugin Container 的 resolveId → load → transform 管线处理后返回浏览器;生产模式下,Rolldown 统一处理全量打包。

图 3.1-1: Vite 核心组件架构图

PlantUML diagram

3.2 包图 — Monorepo 结构

Vite 采用 pnpm monorepo 管理多个子包,包含核心 CLI、脚手架工具、官方插件和兼容层。

图 3.2-1: Vite Monorepo 包结构

PlantUML diagram

3.3 类图 — 核心类关系

Vite 的 JavaScript API 围绕几个关键类构建,展示了插件系统、模块图和运行环境的抽象。

图 3.3-1: Vite 核心类图

PlantUML diagram

3.4 部署图 — 环境拓扑

Vite 6+ 引入了 Environment API,允许框架定义多个运行时环境(client/ssr/edge/worker),每个环境拥有独立的模块解析和执行规则。

图 3.4-1: Vite 多环境部署拓扑

PlantUML diagram

3.5 统一工具链演进

Vite 的工具链经历了重要的演进过程。Vite 原始架构依赖两套工具:开发用 esbuild(Go 语言编译),生产用 Rollup(JS 打包)。Vite 6+ 通过 Rolldown(Rust 打包器)和 Oxc(Rust 工具链)统一为单一线程,消除双管线不一致问题。

图 3.5-1: 工具链演进对比

PlantUML diagram
统一工具链优势

第三部分:用户使用手册

4.1 项目创建

脚手架创建(推荐)

使用 create-vite 交互式创建项目,选择框架和语言:

npm create vite@latest          # npm
yarn create vite               # yarn
pnpm create vite               # pnpm
bun create vite                # bun

交互提示中选择项目名和模板。也可以直接指定模板参数跳过交互:

npm create vite@latest my-app -- --template react-ts
模板预设说明
vanilla / vanilla-ts纯 JS/TS,无框架
vue / vue-tsVue 3 + 官方 Vite 插件
react / react-tsReact + SWC/官方插件
svelte / svelte-tsSvelte
solid / solid-tsSolidJS
lit / lit-tsLit (Web Components)

手动安装

在已有项目中安装 Vite 作为开发依赖:

npm install -D vite

然后在项目根目录创建 index.html:

<!doctype html>
<html lang="en">
  <head><meta charset="UTF-8" /></head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.js"></script>
  </body>
</html>

4.2 项目结构

Vite 项目的核心原则:index.html 是入口,不是藏在 public/ 里的附属品。

my-vite-app/
├── index.html           ← 应用入口(必须位于项目根目录)
├── package.json
├── vite.config.js       ← Vite 配置文件
├── public/              ← 不经处理的静态资源(直接复制到 dist)
│   └── favicon.ico
├── src/
│   ├── main.js          ← 应用入口 JS
│   ├── App.jsx
│   ├── components/
│   │   └── Header.jsx
│   ├── assets/          ← 会被处理/内联的静态资源
│   │   └── logo.svg
│   └── styles/
│       └── global.css
└── tsconfig.json        ← TypeScript 配置(可选)
关键规则

4.3 开发命令

package.json 脚本

脚手架创建的默认 npm scripts:

{
  "scripts": {
    "dev": "vite",              // 启动开发服务器 (别名: vite dev, vite serve)
    "build": "vite build",      // 生产构建
    "preview": "vite preview"   // 本地预览构建产物
  }
}

常用 CLI 选项

选项说明
--port 3000指定端口(默认 5173)
--open自动在浏览器中打开
--host暴露到局域网(如手机测试)
--force强制重新预打包依赖
--mode production指定运行模式(影响 env 变量加载)
--outDir dist指定输出目录 (build 命令)
--watch监听文件变化自动重新构建 (build 命令)
--ssr entry.jsSSR 构建 (build 命令)

查看完整 CLI 选项:npx vite --help

4.4 关键功能

TypeScript

Vite 开箱支持 .ts 文件导入,但只做 转译(transpile),不做类型检查。

// src/main.ts — 直接导入,不需要特殊配置
import { sayHello } from './utils'
console.log(sayHello('World'))
重要
tsconfig.json 中必须设置 "isolatedModules": true,因为 Vite 使用 Oxc 单文件转译。类型检查建议在 IDE 中完成,或在构建时单独运行 tsc --noEmit。

CSS 处理

支持 .css、.scss、.less、.styl 直接导入:

import './style.css'              // 自动注入到页面
import styles from './App.module.css'  // CSS Modules
import cssString from './vars.css?inline'  // 作为字符串导入

CSS Modules:引用 .module.css 后缀自动启用,支持类名映射:

import classes from './Button.module.css'
// classes.primary → 编译后的哈希类名

预处理器安装后即用:

npm add -D sass-embedded   # .scss / .sass
npm add -D less             # .less
npm add -D stylus            # .styl

静态资源

导入资源返回最终部署 URL:

import imgUrl from './hero.png'       // → /assets/hero.hash.png
import rawText from './data.txt?raw'  // → 文本内容
import logo from './logo.svg?url'     // → 显式获取 URL

小文件(默认 <4KB)自动内联为 base64。

环境变量

在源码中通过 import.meta.env 访问:

// 使用 .env 文件定义变量
// .env                — 所有模式通用
// .env.development    — dev 模式
// .env.production     — build 模式

// 源码中访问
console.log(import.meta.env.VITE_API_URL)  // 必须以 VITE_ 前缀
console.log(import.meta.env.PROD)          // 内置布尔值
console.log(import.meta.env.DEV)
console.log(import.meta.env.BASE_URL)

Glob 批量导入

// 懒加载
const modules = import.meta.glob('./pages/*.tsx')
for (const path in modules) {
  modules[path]().then(mod => { /* ... */ })
}

// 静态批量导入
const modules = import.meta.glob('./components/*.tsx', { eager: true })

Web Workers

// 推荐写法(标准方式)
const worker = new Worker(new URL('./worker.js', import.meta.url))

// 查询参数写法
import MyWorker from './worker.js?worker'
const worker = new MyWorker()

4.5 配置基础

Vite 配置文件名为 vite.config.js(或 .ts/.mts),位于项目根目录:

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],

  // 公共基础路径(部署在子路径下时修改)
  base: '/',

  // 开发服务器配置
  server: {
    port: 3000,
    open: true,
    proxy: {
      '/api': 'http://localhost:8080'  // API 代理
    }
  },

  // 构建配置
  build: {
    outDir: 'dist',
    assetsDir: 'assets',
    sourcemap: false,
  },

  // 路径别名
  resolve: {
    alias: {
      '@': '/src',
    },
  },

  // CSS 配置
  css: {
    modules: {
      localsConvention: 'camelCase',
    },
  },
})
环境特定配置
defineConfig 也支持接收一个函数,第一个参数 { mode, command } 可以区分 dev/build 环境:
export default defineConfig(({ command, mode }) => ({
  plugins: [...],
  define: {
    __DEV__: command === 'serve',
    __MODE__: JSON.stringify(mode),
  },
}))

4.6 框架集成

React

npm install -D @vitejs/plugin-react
npm install react react-dom
// vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
})

Vue

npm install -D @vitejs/plugin-vue
npm install vue
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
})

Svelte

npm install -D @sveltejs/vite-plugin-svelte
npm install svelte
// vite.config.js
import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'

export default defineConfig({
  plugins: [svelte()],
})

后端框架集成

Vite 也常被 Laravel、Ruby on Rails 等后端框架用作前端资产管线:

// Laravel 示例 — 使用 middleware mode
import { createServer } from 'vite'
const vite = await createServer({
  server: { middlewareMode: true },
})
// 挂载到 Laravel 的 HTTP 服务器上

4.7 常见问题

修改配置后不生效

修改 vite.config.js 后,dev server 会自动重启。如果没有,尝试手动 Ctrl+C 后重新 npm run dev。

导入 CJS 依赖报错

某些 CommonJS 依赖可能需要显式加入预打包:

// vite.config.js
export default defineConfig({
  optimizeDeps: {
    include: ['some-cjs-package'],
  },
})

浏览器控制台大量 404 请求

检查是否有未预打包的依赖被浏览器按 ESM 请求。可用 vite-plugin-inspect 查看模块解析链路。

HMR 不工作

生产构建产物过大

与 Webpack/Rspack 区别

特性ViteWebpack
开发模式原生 ESM 按需编译全部打包后启动
冷启动速度<1s数十秒(大型项目)
HMR 速度毫秒级秒级
打包引擎Rolldown (Rust)Webpack (JS)
配置复杂度低,开箱即用高,需大量配置
插件生态Rolldown/Rollup 兼容极其丰富

第四部分:生产部署

5.1 部署总览

Vite 的生产部署由两步组成:vite build 生成优化产物 + 将 dist/ 部署到生产环境。

生产构建命令

vite build                          # 标准构建
vite build --watch                  # 监听模式(持续构建)
vite build --mode staging           # 指定模式加载 .env.staging
vite build --outDir prod-dist       # 自定义输出目录
vite build --ssr src/entry-server.ts  # SSR 构建

产物目录结构

dist/
├── index.html                  ← 入口 HTML(含 modulepreload/prefetch 指令)
├── assets/
│   ├── index-abc123.js         ← 主入口 JS(带 hash)
│   ├── vendor-xyz789.js        ← 拆分的第三方包(若启用 code splitting)
│   ├── chunk-*.js              ← 懒加载路由拆分
│   └── index-def456.css        ← CSS(带 hash)
├── images/                     ← 大资源(默认 >4KB 的不内联)
│   └── hero-hash.png
└── .vite/
    └── manifest.json           ← 资源映射表(SSR 用)
关键构建配置(vite.config.js build 选项)
build: {
  outDir: 'dist',              // 输出目录
  assetsDir: 'assets',         // 资源子目录
  assetsInlineLimit: 4096,     // 小于此值(bytes)的资源内联 base64
  sourcemap: false,            // 是否生成 sourcemap('hidden' 隐藏引用)
  minify: 'oxc',               // 压缩引擎: 'oxc' | 'esbuild' | false
  cssMinify: 'lightningcss',   // CSS 压缩: 'lightningcss' | 'esbuild'
  target: 'esnext',            // 浏览器目标(影响降级策略)
  // Rolldown 扩展选项
  rollupOptions: {
    output: {
      manualChunks: {          // 手动控制代码分割
        vendor: ['react', 'react-dom'],
        utils: ['./src/utils'],
      },
    },
  },
  // 产物文件大小警告阈值
  chunkSizeWarningLimit: 500,  // KB
},

部署前本地预览

部署前强烈建议用 vite preview 在本地验证产物:

npx vite preview                  # 默认 port 4173
npx vite preview --port 8080      # 指定端口
npx vite preview --host 0.0.0.0   # 暴露到局域网
preview 不是生产服务器
vite preview 仅用于本地预览构建产物,不适合直接用作生产服务器。生产环境应使用 Nginx、Caddy 等专业 Web 服务器。

5.2 静态托管(Nginx/CDN)

SPA/MPA 最常见的部署方式:vite build → 上传 dist/ 到静态服务器。

Nginx 配置(SPA)

server {
    listen 80;
    server_name example.com;
    root /var/www/my-app/dist;
    index index.html;

    # 静态资源缓存(带 hash 的文件永不过期)
    location /assets/ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # SPA fallback:所有非文件请求回退到 index.html
    location / {
        try_files $uri $uri/ /index.html;
    }

    # gzip 压缩
    gzip on;
    gzip_types text/plain text/css application/json application/javascript text/xml;
    gzip_min_length 1000;
}

Nginx 配置(MPA 多页面)

server {
    root /var/www/my-app/dist;

    # 多页面:每个 .html 对应一个路由
    location / {
        try_files $uri $uri.html $uri/ =404;
    }
}

部署到子路径

如果应用部署在 https://example.com/my-app/ 而非根路径,需设置 base:

// vite.config.js
export default defineConfig({
  base: '/my-app/',     // 以 / 开头和结尾
})

Nginx 对应配置:

location /my-app/ {
    alias /var/www/my-app/dist/;
    try_files $uri $uri/ /my-app/index.html;
}

CDN 部署

将 dist/assets/ 上传到 CDN(OSS/S3/CloudFront),其他文件部署到 Web 服务器:

// vite.config.js — 将资源引用指向 CDN
export default defineConfig({
  base: 'https://cdn.example.com/',
})

5.3 SSR 部署

Vite 内置 SSR 支持,服务端渲染部署分为两步:构建 client + server bundle。

构建配置

{
  "scripts": {
    "build:client": "vite build --outDir dist/client",
    "build:server": "vite build --ssr src/entry-server.js --outDir dist/server",
    "build": "npm run build:client && npm run build:server"
  }
}

SSR 服务端入口示例

// server.js (Node.js / Express)
import express from 'express'
import { createServer as createViteServer } from 'vite'

const app = express()

// 生产模式:直接加载构建产物
if (process.env.NODE_ENV === 'production') {
  app.use(express.static('dist/client'))
  // 加载 SSR manifest 用于资源注入
  const manifest = await import('./dist/client/.vite/manifest.json')
  const { render } = await import('./dist/server/entry-server.js')
  // HTML 渲染逻辑...
}

// 开发模式:创建 Vite dev server(middleware mode)
if (process.env.NODE_ENV === 'development') {
  const vite = await createViteServer({
    server: { middlewareMode: true },
    appType: 'custom',
  })
  app.use(vite.middlewares)
}

Edge Runtime 部署(Cloudflare Workers)

Vite 的 Environment API 原生支持 Edge/Worker 环境,无需 Node.js:

// vite.config.js — 定义 edge 环境
export default defineConfig({
  environments: {
    client: {
      build: { outDir: 'dist/client' },
    },
    ssr: {
      build: { outDir: 'dist/worker' },
      resolve: { conditions: ['worker'] },
      consumer: 'server',
    },
  },
})

5.4 Docker 部署

多阶段构建 Dockerfile(静态 SPA)

# Stage 1: 构建
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Stage 2: 生产 Nginx
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

多阶段构建 Dockerfile(SSR Node.js)

FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:22-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json .
COPY --from=builder /app/server.js .
ENV NODE_ENV=production
EXPOSE 3000
CMD ["node", "server.js"]

docker-compose.yml

version: '3.8'
services:
  app:
    build: .
    ports:
      - "80:80"
    environment:
      - VITE_API_URL=https://api.example.com
    restart: unless-stopped

5.5 CI/CD 集成

GitHub Actions

# .github/workflows/deploy.yml
name: Deploy
on:
  push:
    branches: [main]

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '22'
          cache: 'npm'

      - run: npm ci
      - run: npm run build
      - run: npm run build:ssr  # 如果是 SSR

      # 部署到静态托管(示例:Vercel / Netlify / OSS)
      - name: Deploy to OSS
        uses: some-oss-action@v1
        with:
          source: dist/
          bucket: my-bucket
          endpoint: oss-cn-shenzhen.aliyuncs.com

环境变量管理

Vite 的 env 机制在构建时静态替换(不是在运行时读取)。CI/CD 中通过不同 .env 文件区分环境:

# .env.production        — 生产构建自动加载(vite build 默认 mode=production)
VITE_API_URL=https://api.prod.example.com
VITE_GA_ID=UA-XXXXXXXX-X

# .env.staging           — 预发环境(vite build --mode staging)
VITE_API_URL=https://api.staging.example.com

# .env.preview           — 预览模式(vite preview)
VITE_API_URL=https://api.preview.example.com
安全提醒
VITE_* 变量会内联到客户端代码中,绝不要在其中存放密钥、token 等敏感信息。服务端密钥应放在后端环境变量中,Vite 通过 server.proxy 转发 API 请求即可避免暴露。

构建性能优化(CI 中)


动效 & 动画系统

从 CSS 原生动画到 JS 动效库生态:渲染管线 · 技术分层 · 框架集成 · 选型指南

1. 概述

动效是前端「颜值金字塔」中决定观感的重要一环。本章聚焦 JS 动效库生态,与前面 HTML/CSS/JS/T 章的 CSS 基础动画(Transition / @keyframes / Scroll-driven)互补:那里讲「原生 CSS 怎么做」,这里讲「用库怎么做、选哪个、性能怎么保证」。

动效的本质:在每一帧(约 16.7ms)读取当前时间 → 计算插值 → 更新样式属性,驱动浏览器重绘。决定流畅度的关键是是否触发了布局/绘制,而非库本身。

一句话把握:

1.1 动画 vs 视频 — 先分清两条轴

「界面里明明也有视频,凭什么说视频不是动效?」——视频确实是界面的组成部分,但它与动画属于两条不同的分类轴,容易混淆,先拆开:

组成轴:界面由什么构成

  • 结构:HTML 元素与语义
  • 样式:CSS 外观与布局
  • 行为:JS 交互逻辑
  • 媒体资源:图片 / 视频 / 音频(通过 <video> / <img> 嵌入)

在这条轴上,视频是「内容素材」,和图片、文字同级。

机制轴:元素怎么被呈现

  • 静态呈现:画面不动
  • 动效(动画):代码/声明式实时计算,逐帧渲染
  • 播放(视频):解码预录帧序列,按时间线回放

在这条轴上,动画是「表现/行为机制」,与视频的实现路径完全不同。

一句话:视频是"放进界面里的内容",动画是"让界面怎么动的方式"。两者不互斥——你完全可以给 <video> 加淡入动画、随滚动缩放。本文「动效」章节只讲机制轴的"代码驱动怎么动",不否认视频作为内容存在于界面中。
维度动画(Animation)视频(Video)
本质代码 / 声明式规则实时计算生成,逐帧渲染预录制像素帧文件(MP4 / WebM 等),解码回放
内容可参数化、可随状态变化(CSS / GSAP / rAF 驱动)固定不可变
交互可响应 hover / 滚动 / 拖拽,可中断、可编排只能播放 / 暂停 / 跳转
体积近乎 0(纯代码 / CSS)大,需托管 + CDN + 带宽
清晰度矢量缩放不糊超过原始分辨率会糊
性能transform / opacity 走 GPU 合成,可保 60fps解码耗 CPU / GPU,帧率固定
典型用途UI 微交互、转场、加载、视差实拍、录屏、叙事性内容、真实画面
模糊地带(介于两者之间):

第一部分:行为图(动效如何工作)

图 2-1: 动效渲染管线时序图 — 一帧动画从 JS 到屏幕

PlantUML diagram
性能铁律(决定帧率的核心):只动画 transform 和 opacity。它们只触发 Composite(合成)阶段,不回 Layout/Paint,能稳定跑满 60fps。动画 width / height / top / left / margin 会反复触发布局,最昂贵。

第二部分:结构图(技术分层)

图 3-1: 动效技术分层 — 从原生到 3D

PlantUML diagram
层次代表适用场景控制力
浏览器原生CSS transition / @keyframes、Web Animations API、rAF简单动效、微交互、滚动视差低(声明式为主)
动效库GSAP、Motion (Framer Motion)、Motion One、Anime.js、VueUse Motion编排队列、时间轴、缓动曲线、复杂交互动画高(时间轴/缓动全掌控)
框架集成/组件Framer Motion (React)、@vueuse/motion、Transition/TransitionGroup、AutoAnimate声明式地给组件加过渡、列表进入/离开中(贴近框架语法)
扩展/3DThree.js / WebGL / Canvas 2D / Lottie(SVG)3D、游戏、数据可视化、导入 AE 动画极高(底层图形)

第三部分:选型手册

图 4-1: 动效选型决策

PlantUML diagram

4.2 主流动效库指南

库技术栈核心优势典型场景
GSAP框架无关业界最强时间轴(Timeline)、从容编排、性能好、生态全落地页、复杂时间轴、跨元素编排
Motion (Framer Motion)React / 通用声明式、Spring 物理、AnimatePresence(进出场)React 组件微交互、页面过渡
Motion One通用(轻量)基于 Web Animations API,体积小、性能优追求极致体积的动效
VueUse MotionVue 3组合式 API 编写动效、与 Vue 响应式协同Vue 项目的声明式动效
Anime.js框架无关体积小、API 简洁、支持 SVG中小型动画、SVG 动效
Three.jsWebGL3D 场景、相机、材质、渲染3D 展示、数据可视化、游戏
LottieSVG/Canvas导入 After Effects 动画 JSON,保真度高复杂品牌动画、图标动画

4.3 性能与最佳实践