CLASS 17 · 2026-04-13 · 17-19 系统与界面
回调与图形用户界面
回调是「把函数交给别人,让别人到时候再调用」的编程手法。它驱动着异步操作、定时器和图形界面的事件处理,是理解事件循环与 Listener 模式的核心。
回调
回调(callback):客户端提供给模块的一个函数,供模块在合适的时机调用。
回调让控制流发生「反转」:不是我们主动调用库,而是库在事件发生时调用我们提供的函数。
同步回调 vs. 异步回调
- 同步回调:在调用方执行期间立即运行,比如数组的
map、filter。 - 异步回调:被保存起来,留到未来的某个事件发生时再运行,比如网络请求完成、按钮被点击。
// 同步回调
[1, 2, 3].map(x => x * 2);
// 异步回调:1 秒后运行
setTimeout(() => console.log("hello"), 1000);
定时器回调
setTimeout(callback, ms) 是最简单的异步回调:它把回调放入队列,至少等到指定毫秒后才执行。注意「至少」——如果事件循环正忙,实际等待可能更久。
事件循环与事件队列
TypeScript/JavaScript 用事件循环(event loop)和事件队列(event queue)来执行异步回调。主线程不断从队列中取出任务执行;当任务发起异步操作时,它的回调被排入队列,等待下一轮处理。
事件循环:不断从事件队列取出回调并执行的机制,保证了单线程下异步任务的有序调度。
用 Promise 组合回调
Promise<T>.then(callback) 把一个回调附加到 Promise 上,并在 Promise 完成时调用它,同时返回一个新的 Promise。这是把回调风格与 Promise 风格桥接起来的关键。
fetch("/api/user")
.then(res => res.json()) // 回调 1:解析 JSON
.then(user => user.name) // 回调 2:取出名字
.then(name => console.log(name)); // 回调 3:打印
拆解 async 函数
await 并不是魔法——它的本质是:把 await 之后的剩余计算包装成一个 then 回调,附加到被等待的 Promise 上。理解这一点,就能在回调与 async/await 之间自由切换。
// 这两种写法在语义上等价:
async function f() {
const x = await p;
console.log(x);
}
function f() {
return p.then(x => console.log(x));
}
HTML 与 DOM
HTML 元素构成一棵树,称为文档对象模型(Document Object Model, DOM)。JavaScript 可以读取和修改这棵树,从而动态改变页面内容和结构。
输入处理:Listener 模式
GUI 事件处理采用监听者模式(Listener pattern):
- 事件源(event source):产生事件的对象,比如按钮。
- 监听者(listener):订阅事件并提供回调的模块。
const button = document.querySelector("#submit");
button.addEventListener("click", (event) => {
console.log("按钮被点击了");
});
监听者的常见陷阱
- 要快:监听者必须尽快返回,否则会阻塞事件循环,让界面卡顿。
- 警惕循环:监听者之间互相触发可能形成无限循环。
- 记得清理:不再需要的监听者要移除(
removeEventListener),否则会造成内存泄漏和意外行为。
关键要点
- 回调客户端提供给模块、由模块在适当时机调用的函数。
- 同步/异步同步回调立即执行,异步回调排入事件队列。
- 事件循环单线程下通过事件队列有序调度异步任务。
- thenPromise.then 把回调附加到完成时,返回新 Promise。
- 拆解 awaitawait 等价于把剩余计算包装成 then 回调。
- ListenerGUI 用监听者模式处理事件,注意快执行、防循环、做清理。