构建 Web 应用 — 浏览器按什么顺序干活,JSX 把 DOM 藏到了哪
构建 Web 应用 — 浏览器按什么顺序干活,JSX 把 DOM 藏到了哪
这一章讲三件事: 浏览器拿到一段 HTML 后,是按什么顺序把它变成页面的; 「事件」这种随时可能发生的东西,在单线程的世界里是怎么被排队和处理的; 以及 React 的 JSX 把前面那些手工 DOM 操作藏到了哪里、代价是什么。 读完你会明白:为什么一个卡死的页面恢复后会连响应你三次点击。
1. 顶层全景:一个 Web 应用只有两个阶段
原书把客户端 Web 应用的生命周期压成一张图(我们重画成文字版):1
用户输入网址 / 点击链接
│
▼
浏览器向服务器发请求 ──→ 服务器处理,返回 HTML + CSS + JavaScript
│
▼
┌─ 阶段一:建页(Page building)
│ 解析 HTML,一棵节点一个节点地搭出 DOM
│ 遇到 <script> → 暂停搭建,执行脚本(脚本能改 DOM),执行完继续搭
│ HTML 耗尽 → 建页完成
│
└─ 阶段二:事件处理(Event handling)
循环:看队列头部有没有事件 → 有就取出、执行绑定的处理器 → 没有就继续看
(队列:用户点击/按键、网络响应、定时器……全排在同一列)
│
▼
用户关闭或离开页面 —— 生命周期结束这里有两个贯穿全章的判断:
第一,Web 应用是 GUI 应用。 图形界面应用的宿命就是「大部分时间无所事事,偶尔响应一下用户」, 所以它的生命必然是「先搭界面,再进循环等事件」的两段式——桌面应用、手机应用同构。2
第二,两个阶段的边界是 DOM 的完成,而 JavaScript 在两个阶段都能插手。 建页时脚本能改 DOM(页面还没建完就能改),事件处理阶段脚本靠处理器持续改 DOM。 理解 DOM(见下)是理解一切的钥匙。
DOM(Document Object Model,文档对象模型) 是浏览器在内存里维护的一棵树, 树上的每个节点对应页面上的一个元素。HTML 是图纸,DOM 是照图纸盖出来的房子; JavaScript 不能直接改 HTML 文本,它改的是 DOM——改了 DOM,浏览器就把页面的显示更新过来。3
主线例子:一个「按键记事本」页面
书里用这个示例贯穿全章(我们照抄其骨架)。页面有一个列表 <ul id="event-list">,
脚本做三件事:定义一个往列表里加消息的函数;注册一个「DOM 建完」事件的处理器;
注册一个「按下键盘」事件的处理器;最后在脚本末尾直接加一条消息。4
在浏览器里打开这个页面,敲下 asdf 四个键,你会看到——注意顺序:
End of script reached ← 脚本末尾那句,建页阶段就执行了
Event "DOMContentLoaded" fired ← 建页完成的事件
Event "keydown" fired with key = "a"
Event "keydown" fired with key = "s"
Event "keydown" fired with key = "d"
Event "keydown" fired with key = "f"第一句话出现在最前面,是本章第一个反直觉点:它证明脚本在建页阶段内就执行完了, 而不是等页面全部就绪。后面四行为什么是那个顺序,第 3 节回答。
2. 建页阶段:解析、DOM,以及 script 的「插队权」
2.1 HTML 怎样变成一棵树
浏览器拿到 HTML 后从头解析,一个元素一个元素地建节点。到第一个 <script> 时,
解析器身后的 DOM 长这样(文字版示意):5
html
├── head
│ └── (title、style 等已解析的节点)
└── body
└── ul#event-list ← 脚本执行时,这个元素已经存在
└── (脚本即将加进来的 li)树的规矩:除根节点外每个节点恰有一个父节点;一个节点可以有任意多个孩子; 同一父节点下的孩子们互称兄弟。这套家谱词汇是 DOM API 的操作对象。
「脚本执行时它要找的元素必须已经存在」——这就是书上示例把 <ul> 写在 <script> 前面的原因,
也是无数「为什么 querySelector 返回 null」的答案。<script> 的默认行为是阻塞:
见到它就停下解析、执行完再继续(可以用 async、defer、type="module" 等属性改变,
书指向 MDN,不展开)。6
2.2 HTML ≠ DOM:浏览器会替你改图纸
一个容易忽略的要点:HTML 和 DOM 关联紧密,但不是同一个东西。 HTML 只是初版图纸,浏览器发现图纸有错会静默修正。书里给了一种典型错误: 有人把该放进 body 的内容段落写进了 head——浏览器会不声不响地把它挪进 body, 构出它认为合法的 DOM。原书一句话点破:把 HTML 当成图纸,把 DOM 当成房子。7
顺带一个边界的知识(书里边栏):HTML 和 DOM 各有一份「活标准」, 而且 DOM 标准是中立于 HTML 的——矢量图格式 SVG 用的也是同一套 DOM。 实战上,MDN 文档比规范原文更有用。8
2.3 脚本执行时发生了什么
回到主线例子的脚本,逐行走一遍:9
const eventList = document.querySelector("#event-list"); // ① 按 CSS 选择器找到那个 ul
const appendToEventList = (message) => { // ② 定义函数(此时不执行)
const messageElement = document.createElement("li"); // 造一个新节点
messageElement.textContent = message; // 填文本
eventList.appendChild(messageElement); // 挂到 ul 下
};
document.addEventListener("DOMContentLoaded", () => { // ③ 登记:DOM 建完时叫我
appendToEventList('Event "DOMContentLoaded" fired');
});
document.addEventListener("keydown", (evt) => { // ④ 登记:有按键时叫我
appendToEventList(`Event "keydown" fired with key = "${evt.key}"`);
});
appendToEventList("End of script reached"); // ⑤ 立刻执行:加第一条三件事分清楚:document.querySelector 返回的是 DOM 树上的现成节点(第 ① 步);
document.createElement 造的是游离的新节点(第 ② 步里),appendChild 把它挂上树;
第 ③④ 步的 addEventListener 只是登记,处理器此刻不跑;第 ⑤ 步是唯一在建页阶段就立刻执行的加节点操作。
脚本跑完后,浏览器接着解析剩余 HTML,然后进入第二阶段。一个重要细节:
脚本之间共享全局状态——一个 script 标签里定义的顶层变量,后面的 script 标签直接用,
因为它们都活在同一个全局 window 对象上。10
3. 事件处理:一条队列、一个线程、三次点击之谜
3.1 银排队列
浏览器执行环境的核心纪律叫单线程执行模型:任何时刻只有一段代码在跑。 原书用的比方是银行柜台——只有一个窗口,客户(事件)排成一列,办完一个办下一个; 只要有一个客户在窗口里做「全年财务规划」,全队卡住。11
但用户不会乖乖按节奏触发事件。浏览器需要一个地方暂存「已经发生但还没处理」的事件—— 事件队列(event queue)。所有事件,不管来源(用户按键、网络响应、定时器),都按被探测到的先后 进同一条队列。处理循环只有三行逻辑:12
1. 看队列头部
2. 空 → 继续看(空转等待)
3. 有 → 取出,执行绑定的处理器;处理期间,其他事件在队列里等「卡死的页面恢复后连响应三次点击」之谜,在这里解开: 把事件放进队列的机制, 独立于处理事件的线程——它在外面探测「用户点了」,只管往里放。 页面被一段昂贵的 JavaScript 卡死时,点击照样被探测、照样入队; 等线程空出来,三个点击事件挨个取出、挨个执行,于是按钮的处理器连跑三次。13
推论立刻出现:处理器跑多久,页面就卡多久。 处理器的执行时间直接等于界面的无响应时间——这是「不要阻塞线程」在第 1 章之后的第二次出现, 也是 Web 性能里最硬的一条纪律。
3.2 事件的三个来源与异步的本性
会发生的事件不限于用户操作,原书列了四类:浏览器事件(页面加载完成、即将卸载)、 网络事件(fetch 的响应)、用户事件(点击、移动、按键)、定时器事件(超时到期、promise 落定)。 它们共同的脾气是异步(asynchronous):发生的时间和顺序不受你控制—— 「强迫用户按某个顺序按键」是做不到的。14
由此得出一个统计性的事实:一个典型 Web 应用里,绝大部分运行的代码都跑在事件处理器里。 这句话值得记住,因为它解释了为什么这本书用四章讲函数——事件处理器就是回调, 回调就是函数作为一等对象的应用。
3.3 注册处理器,以及一个事件的小魔术
addEventListener(事件名, 处理器) 是登记的标准姿势,主线例子的 ③④ 步已经演示。
处理器是普通的函数值——又一次,「函数是值」在干活。15
有个细节初读会意外:keydown 事件是从键盘焦点所在元素发出的,
可书上的处理器挂在 document 上,为什么在输入框里按键也能触发?
答案是 DOM 的冒泡(bubbling):事件从发生它的节点出发,逐级向上经过父节点直到 document,
沿途的每个节点都有机会拦下它。所以「在哪里注册」和「在哪里发生」可以不是同一个节点。
(冒泡与它的近亲「捕获」的完整规则,原书安排在后续章;本章只要知道「事件会向上冒」这一件事。16)
4. JSX:把「操作 DOM」改写成「描述状态」
到此为止,页面内容是直接写 HTML、用 DOM API 手工改的。2010 年代中期,React 带来另一种姿势: JSX——一种把 HTML 语法嵌进 JavaScript 的语言扩展。看这一行:17
const link = <a href="https://manning.com">Manning</a>;它不是 HTML 字符串,而是一个值。JSX 编译成普通 JavaScript 后,标签变成了函数调用:
const link = _jsx("a", { href: "https://manning.com", children: "Manning" });_jsx 来自所选运行时(React 是原创者,也有别的实现):参数是标签名和一个属性对象,
返回值是页面的一个「描述对象」。——这一步看清,JSX 的神秘感就消失了:
它只是一段生成普通对象的语法糖。
用 JSX 重写主线例子,形态大变:不再有 createElement/appendChild/addEventListener,
而是一个「组件」函数——输入是状态(一个事件字符串数组),输出是「页面应该长什么样」:18
import React, { useState } from "react";
export default function EventList() {
const [events, setEvents] = useState(["Initial event"]); // 状态:事件列表
return (
<ul id="event-list">
{events.map((str) => <li>{str}</li>)} {/* 每个事件一个条目 */}
<li>
<input onKeyDown={(evt) => { {/* 按键处理器 */}
setEvents(events.concat(`keydown: ${evt.key}`)); {/* 状态更新 */}
}} />
</li>
</ul>
);
}注意按键处理器挂在 <input> 上而不是 document(在 React 里挂 document 需要额外的 hook,书中略去)。
神奇之处:对数组的任何修改——加、删、改——React 会自动把 DOM 更新到和你的输出一致。
你从此只思考「状态 → 界面」,不思考「哪个节点插哪」。书里说得很直白:没有 createElement,
没有 appendChild,没有 addEventListener。19
4.1 虚拟 DOM:这份省心的价格
React 凭什么做到?它在内部维护一棵虚拟 DOM——各组件输出拼成的「页面应有的样子」, 一份自己的树。每次状态变化,它重新渲染出新的虚拟 DOM,和旧版做一次比对 (这个过程叫 reconciliation,调和),只把差异部分应用到真 DOM 上。20
这套机制统治了 Web 开发,但原书立刻记下它的代价,而且这代价正是第 1 章「代码膨胀/SSR」主题的落地:
- 用 JavaScript 对象描述一个节点,比等价的 HTML 标签更大、解析更贵;
- 框架自身的代码量不小,React 本身要先下载、先执行;
- 打包出来的 bundle 可能包含用户此刻看不到的页面、甚至别的页面的代码。
对策三件套(原书的「忍者责任」清单):考虑更轻的框架(如 Solid);把 bundle 切块,首屏用不到的延后加载; 能用 SSR 就用 SSR——同一份代码先在服务器上渲染成完整 HTML,浏览器先见内容、后跑脚本。21
5. 作者的判断与证据
本章的机制性内容(解析阻塞、队列行为、JSX 编译形态)全部可由读者亲手复现,原书给出了完整的 HTML 与命令行输出作为证据,包括「End of script reached 出现在 DOMContentLoaded 之前」这个 决定阶段划分的观察。22
作者立场(当立场读):
- 「框架要负责任地用」及 Solid/SSR 建议,是作者的价值判断,React 生态的现实惯例未必一致;
- 「virtual DOM 节省了直接操作 DOM 的麻烦」是社区共识级的说法,但「直接操作 DOM 一定慢」 是更早时代的经验,原书没有卷入这场争论,只陈述「比对后只更新差异」的机制本身。
6. 边界与局限
- 事件系统只讲了冒泡的一句。 捕获阶段、
stopPropagation、自定义事件, 原书都标注在后续章——MEAP 里没有。 - React 示例以「看懂机制」为限。
useState为什么返回数组、hook 的规则, 都不在讲;别拿这章当 React 入门。 <script>的 async/defer/module 差异 只给了名字和 MDN 链接,没有行为对比表。- 「渲染不是 JavaScript 做的」 一句在后续章节(微任务)里还会回来,本章先不展开。
7. 可带走的(合上这一章,你应该有这八条)
- Web 应用生命 = 建页 + 事件处理;分界线是 DOM 搭完,而脚本在建页中途就会插队执行。
- DOM 是内存里的树,HTML 只是图纸;浏览器会静默修正图纸的错误;脚本只能改 DOM,不能改 HTML。
<script>默认阻塞解析,且脚本执行时只能访问它之前的节点——顺序写反,querySelector 给你 null。- 事件全部进同一条队列,放入队列的机制独立于执行线程——所以卡死时的点击会被攒着,恢复后连响应。
- 处理器执行时间 = 页面卡死时间;单线程模型的第一纪律再次生效。
- 处理器就是回调,而回调是一等函数——这就是这本书用四章讲函数的原因。
- JSX 编译成普通函数调用(
_jsx(标签, 属性)),组件 = 「状态 → 界面描述」的函数。 - 虚拟 DOM 比对后只更新差异;代价是 JS 体积与启动时间,对策是轻框架、切包、SSR。
9. 深读一:建页阶段的逐步剧本,从字节到树
第 2 节讲了原则和一次脚本执行,这里把浏览器从「收到响应」到「DOM 就绪」的完整剧本 按时间轴铺开——时间轴上每个刻度,都对应一个可以观察的现象。
t0 浏览器收到 HTML 的第一块字节,解析器开工
t1 逐元素建节点:<!doctype>、<html>、<head>、<title>、<style>……
t2 遇到 <script>(无 async/defer)──► 解析暂停,DOM 建设停工
t3 脚本执行:能读到 t2 之前已建好的全部节点
t4 脚本结束(可能改了 DOM)──► 解析复工,接着 t1 的节奏往下建
t5 HTML 耗尽:DOM 建完,DOMContentLoaded 事件入队等待处理
t6 进入事件处理阶段三个可以亲手验证的推论:
- 脚本执行时,后面的元素还不存在:把
querySelector("#event-list")移到<ul>之前,得到 null——顺序就是生命线; - 脚本之间共享全局状态:同一页面两个 script 标签,后一个能读前一个的顶层变量,
因为它们都活在同一个全局
window对象上——这也解释了为什么老代码里 「先引 jQuery 再引插件」的顺序不能换;1 - DOMContentLoaded 是事件:它同样进事件队列——所以注册得晚于它触发,处理器就不执行。 「事件随时发生,处理器必须提前登记」,本章的纪律对浏览器自己的事件同样成立。
9.1 HTML 纠错的完整样例
书里的 Figure 4.4 给了一种典型错误:把内容段落 <p> 写进了 <head>。
浏览器的处理不是报错,而是挪:head 只该放页面元信息(标题、字符集、外部样式表
和脚本的引用),内容属于 body——解析器发现图纸不对,就按自己的理解重画,
段落被悄悄放进 body。「浏览器会修你的 HTML」是把双刃剑:页面能跑,
但 DOM 和你写的 HTML 已经不是一回事,调试时别以为它们相等。2
顺带一条边栏级的知识(书里给了):HTML 和 DOM 各有一份活标准(living standard), 而且 DOM 标准是平台中立的——连矢量图格式 SVG 都复用它。实战上 MDN 比规范原文 好用,但知道「DOM ≠ HTML 的附属品」有助于理解为什么它有自己的规范。
10. 深读二:事件队列的完整运转,与「冻结页面」的微观解剖
10.1 队列的三行循环,配一张时刻表
三行循环(看队头、空则再看、有则取出执行)配上书里的按键例子,时刻表长这样:3
时刻 发生的事 队列(头→尾) 线程在干嘛
─────────────────────────────────────────────────────────────────────
t0 页面加载完成,进入事件处理 [ DOMContentLoaded ] 空转检查
t1 取出 DOMContentLoaded [ ] 执行它的处理器
t2 用户敲下 a [ keydown(a) ] 空转检查
t3 取出 keydown(a) [ ] 执行处理器:列表加一行
t4 敲 s、d、f(假设很快) [ keydown(s,d,f) ] 空转检查
t5 逐个取出执行 …… 列表连加三行时刻 t4 一行值得停顿:三个按键事件排成一队等处理,哪怕它们几乎同时发生。 「单线程」不是「同时只处理一个事件」的优雅说法,而是硬约束:处理器执行期间, 队列只进不出。
10.2 冻结的微观解剖:三次点击的完整时间线
把书里的「卡死按钮」例子展开成时间线:4
0ms 用户点击 ──► 事件入队 [click] ──► 处理器开始执行一段 5 秒的昂贵计算
50ms 用户再点 ──► 入队 [click] (线程仍卡在第一个处理器里)
80ms 又点 ──► 入队 [click] (入队机制独立运行,不受线程卡住影响)
5000ms 第一个处理器终于跑完 ──► 队列出队:第二个 click 执行
~0ms 第二个跑完 ──► 第三个 click 执行
结果:用户明明「点了一下没反应又点了两下」,最后按钮响应了三次冻结期间页面为什么不响应任何交互(滚动、别的按钮)?因为所有事件的处理器 都等同一个线程。这条机制同时解释了 Web 性能的第一定律: 处理器的时长 = 页面无响应的时长——「不要阻塞线程」在浏览器侧的精确含义。5
10.3 事件的四类清单,与异步的定义
书里把事件分了四类,值得背下来作为「什么东西会触发我的处理器」的检查表:6
| 类别 | 例子 | 来源 |
|---|---|---|
| 浏览器事件 | 页面加载完成、即将卸载 | 浏览器自身 |
| 网络事件 | fetch 的响应到达 | 服务器/网络栈 |
| 用户事件 | 点击、移动、按键 | 输入设备 |
| 定时器事件 | 超时到期、promise 落定 | 定时器/promise 机制 |
它们被统称为异步的根据:发生的时间不可预测、顺序不可预测 (「强迫用户按某个顺序按键」做不到)。原书给了一个统计性判断: 典型 Web 应用里绝大部分运行的代码都跑在事件处理器里—— 这就是为什么「事件处理器 = 回调 = 一等函数的应用」这条链是本书的心脏。
11. 深读三:JSX 编译的完整机制,与虚拟 DOM 的账
11.1 从标签到函数调用,再进一步
第 4 节给了 <a> 的一行编译结果,把 React 版「事件列表组件」的编译形态
在脑子里过一遍(书里没有逐行展开编译产物,但机制相同):7
<ul id="event-list"> _jsx("ul", { id: "event-list",
{events.map((str) => ( children: events.map((str) =>
<li>{str}</li> ──编译──► _jsx("li", { children: str })
))} )
<li><input onKeyDown={…} /> _jsx("li", { children: _jsx("input",
</ul> { onKeyDown: … }) })
})
})两个观察:其一,JSX 嵌套 = 函数调用嵌套——HTML 的层级就是 _jsx 调用的层级,
树的形状在编译期就定好了;其二,{…} 里的 JavaScript 原样保留——
events.map(...) 是普通数组方法,onKeyDown 只是个属性值。
读懂这两条,React 模板里的任何写法你都能预测它的运行形态。8
11.2 虚拟 DOM 调和的完整账
reconciliation 的过程,书里给了三步,展开成一次具体的状态更新:
状态变化:setEvents([...events, "keydown: x"])
│
▼
① React 重新执行组件函数,得到一棵新的虚拟 DOM 树(内存里的描述对象,不是真 DOM)
② 和旧虚拟 DOM 树逐节点比对(调和):ul 没变、li 列表多了一项、input 没变
③ 把差异应用到真 DOM:只对「多出来的那个 li」做一次真实的 DOM 插入账本的另一面(书里的三宗代价,配数字感):JS 对象描述节点比 HTML 标签体积更大——
一个 <div class="x"> 标签十几个字节,对应的对象描述含标签名、属性表、子节点数组,
几十字节起;框架自身是一大段必须先下载执行的代码;打包器若不做代码分割,
首屏还会带上别的页面的代码。三个对策按性价比排:SSR(根治「空页面等 JS」)、
代码分割(根治「带了一堆用不到的」)、轻量框架(根治「框架本身太大」,
书里点了 Solid 的名)。9
12. 原文地图
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| 生命周期两阶段 | Chapter 4: Building web applications | text/07-ch04-chapter-4-building-web-applications.txt:23(搜「lifecycle」) |
| 按键记事本示例与输出顺序 | 同上 | :51(搜「Listing 4.1」) · :107(搜「End of script reached」) |
| HTML 解析与 DOM 树 | 同上 | :119(搜「tree representing the contents」) · :145(搜「exactly one parent」) |
| script 阻塞与 async/defer | 同上 | :123(搜「stop parsing the document」) |
| HTML 与 DOM 的区别及浏览器纠错 | 同上 | :150(搜「blueprint」) · :158(搜「silently fixes」) |
| 脚本执行逐行 walk-through | 同上 | :180(搜「Executing JavaScript code」) |
| 全局状态跨 script 持久 | 同上 | :216(搜「persists in the meantime」) |
| 单线程与银行比方 | 同上 | :235(搜「single-threaded execution model」) |
| 事件队列三行循环 | 同上 | :254(搜「event queue」) · :256(搜「checks the head」) |
| 三次点击之谜 | 同上 | :269(搜「external to the page-building」) |
| 事件四类与异步定义 | 同上 | :277(搜「EVENTS ARE ASYNCHRONOUS」) |
| addEventListener 与冒泡 | 同上 | :294(搜「Registering event handlers」) · :314(搜「bubbling」) |
| JSX 编译为 _jsx 调用 | 同上 | :321(搜「Writing interactive HTML with JSX」) · :330(搜「compiles to JavaScript」) |
| 事件列表组件重写 | 同上 | :347(搜「Listing 4.3」) |
| 虚拟 DOM 与 reconciliation | 同上 | :387(搜「virtual DOM」) |
| 框架的三宗代价与对策 | 同上 | :392(搜「come at a cost」) |
| 全章总结 | 同上 | :410(搜「Summary」) |
Footnotes
-
出处:「Chapter 4: Building web applications」第 23–46 段(
text/07-ch04-chapter-4-building-web-applications.txt:30,搜「clicking a link) and ends when the user」)。两阶段划分是原书 Figure 4.1 的文字化。 ↩ ↩2 -
出处:「Chapter 4: Building web applications」第 37–46 段(
text/07-ch04-chapter-4-building-web-applications.txt:37,搜「Graphical User Interface」)。Web 应用与其他 GUI 应用同构的论断。 ↩ ↩2 -
出处:「Chapter 4: Building web applications」第 119–133 段(
text/07-ch04-chapter-4-building-web-applications.txt:122,搜「tree representing the contents」)。DOM 定义与「脚本可以访问和修改 DOM」。 ↩ ↩2 -
出处:「Chapter 4: Building web applications」第 47–118 段(
text/07-ch04-chapter-4-building-web-applications.txt:51,搜「Listing 4.1」)。示例程序结构与「我们会用它贯穿全章」(第 47 段)。 ↩ ↩2 -
出处:「Chapter 4: Building web applications」第 134–153 段(
text/07-ch04-chapter-4-building-web-applications.txt:138,搜「first script element」)。遇到第一个 script 时的部分 DOM(Figure 4.3)与家谱词汇(第 145–148 段)。 ↩ ↩2 -
出处:「Chapter 4: Building web applications」第 123–127 段(
text/07-ch04-chapter-4-building-web-applications.txt:123,搜「When the parser encounters a <script> tag, the」)。script 阻塞的默认行为与 async/defer/module 例外。 ↩ ↩2 -
出处:「Chapter 4: Building web applications」第 150–163 段(
text/07-ch04-chapter-4-building-web-applications.txt:151,搜「blueprint the browser follows」)。「HTML 是图纸」的比方与浏览器静默修正段落的例子。 ↩ ↩2 -
出处:「Chapter 4: Building web applications」第 165–178 段(
text/07-ch04-chapter-4-building-web-applications.txt:165,搜「THE HTML AND DOM SPECIFICATIONS」)。DOM 标准中立于 HTML、SVG 共用一套的边栏。 ↩ ↩2 -
出处:「Chapter 4: Building web applications」第 180–224 段(
text/07-ch04-chapter-4-building-web-applications.txt:184,搜「walk through the code」)。脚本逐行执行说明与 Figure 4.5 的 DOM 状态。 ↩ ↩2 -
出处:「Chapter 4: Building web applications」第 214–220 段(
text/07-ch04-chapter-4-building-web-applications.txt:217,搜「persists in the meantime」)。全局状态跨 script 标签共享的机制(全局 window 对象)。 ↩ -
出处:「Chapter 4: Building web applications」第 235–246 段(
text/07-ch04-chapter-4-building-web-applications.txt:237,搜「single-threaded execution model」)。银行柜台比方原文。 ↩ -
出处:「Chapter 4: Building web applications」第 246–268 段(
text/07-ch04-chapter-4-building-web-applications.txt:247,搜「event queue」)。队列的角色与三行处理流程(第 256–263 段)。 ↩ -
出处:「Chapter 4: Building web applications」第 269–275 段(
text/07-ch04-chapter-4-building-web-applications.txt:269,搜「It’s important to note that the browser mechanism」)。入队机制独立于处理线程——「连点三下、响应三次」的解释原文。 ↩ -
出处:「Chapter 4: Building web applications」第 277–291 段(
text/07-ch04-chapter-4-building-web-applications.txt:277,搜「EVENTS ARE ASYNCHRONOUS」)。事件四类清单与异步定义。 ↩ -
出处:「Chapter 4: Building web applications」第 292–313 段(
text/07-ch04-chapter-4-building-web-applications.txt:298,搜「addEventListener method」)。注册处理器即「通知浏览器你有兴趣」。 ↩ -
出处:「Chapter 4: Building web applications」第 314–319 段(
text/07-ch04-chapter-4-building-web-applications.txt:319,搜「bubbling」)。事件可从不同节点拦截、冒泡的引入;捕获在后续章。 ↩ -
出处:「Chapter 4: Building web applications」第 321–341 段(
text/07-ch04-chapter-4-building-web-applications.txt:328,搜「const link = <a href」)。JSX 的一行示例与其「值」的身份。 ↩ -
出处:「Chapter 4: Building web applications」第 330–341 段(
text/07-ch04-chapter-4-building-web-applications.txt:330,搜「converted to objects」)。HTML 标签编译为对象、_jsx函数调用形态。 ↩ -
出处:「Chapter 4: Building web applications」第 343–385 段(
text/07-ch04-chapter-4-building-web-applications.txt:347,搜「Listing 4.3」)。事件列表组件重写与「There's no createElement, no appendChild, no addEventListener」的原文(第 380 段)。 ↩ -
出处:「Chapter 4: Building web applications」第 387–391 段(
text/07-ch04-chapter-4-building-web-applications.txt:387,搜「React provides this simple and elegant developer experience」)。虚拟 DOM 定义与 reconciliation 的机制描述。 ↩ -
出处:「Chapter 4: Building web applications」第 392–408 段(
text/07-ch04-chapter-4-building-web-applications.txt:392,搜「come at a cost」)。三宗代价清单与 Solid/切包/SSR 对策。 ↩ -
出处:「Chapter 4: Building web applications」第 107–115 段(
text/07-ch04-chapter-4-building-web-applications.txt:107,搜「you should see this output」)。输出顺序作为阶段划分的可复现证据。 ↩