第 4 章的语言基础加本章的现代能力,才凑齐「写今天的 JS」所需的全部装备:一套顺手的语法糖(解构、可选链)、一套异步模型(Promise/async,前端的心脏)、模块化(ESM),以及理解所有异步行为底层的事件循环。事件循环是全章最有价值的概念,它同时解释了 setTimeout 的排序、异步的并发模型、和「页面为什么卡」,值得读到能给别人讲出来。
const { name, age } = user; // 解构:一行拆对象(React 组件天天用)
const [first, ...rest] = [1, 2, 3]; // 数组解构 + 剩余
const msg = `你好 ${name},共 ${a + b} 元`; // 模板字符串:告别加号拼接
const merged = { ...defaults, ...options }; // 展开:浅合并对象
const city = user?.address?.city; // 可选链:中间断掉返回 undefined,不炸
const port = config.port ?? 3000; // 空值合并:仅 null/undefined 用默认值
可选链和空值合并值得强调差别:x ?? d 只在 null/undefined 时取默认,0 和 "" 是合法值(取配置项 0 时不被误杀;对比 x || d 会把 0 也吞掉,这是经典 bug)。函数参数默认值、链式 API(sb.append().append() 那种)都是本章的常识底座。语法糖没有理解成本,只有「用过就回不去」的体验差。
JS 单线程,不能阻塞等待网络(等的话页面全冻结),所以一切耗时操作都是异步的。Promise 是异步结果的容器,三状态一次性落定(pending → fulfilled/rejected):
// 链式风格
fetch("/api/user")
.then(res => res.json()) // then 里返回新 Promise,链自然接续
.then(data => console.log(data))
.catch(err => console.error(err)); // 链上任何一环出错都汇到这里
// async/await:同样的流程,同步的写法
async function loadUser() {
try {
const res = await fetch("/api/user");
return await res.json();
} catch (err) {
console.error(err);
}
}
async/await 是 Promise 的语法糖(async 函数必然返回 Promise,await 暂停当前函数但不阻塞线程,控制权交回事件循环)。两套都要会:日常写 await,工具函数与组合器用 Promise 系列:
const [user, posts] = await Promise.all([getUser(), getPosts()]); // 并发!
await Promise.allSettled(tasks); // 全部落定(含失败),批处理
一个高频坑:并发丢失。await a(); await b(); 是串行(总耗时相加),需要并发时先发起再分别 await,或直接 Promise.all。这在「串行加载两个接口,页面慢一倍」的性能问题里天天出现。
JS 官生来没有模块,社区先造了 CommonJS(Node 的 require),ES6 定了标准 ESM:
// ESM(现代标准)
export const name = "Tom";
export default function main() {}
import main, { name } from "./m.js";
// CommonJS(旧 Node,历史)
module.exports = { name };
const { name } = require("./m.js");
本质差异:ESM 的 import/export 是编译期静态结构(引擎解析阶段就知道依赖图),因此构建工具能做 tree-shaking(摇掉没用到的导出,第 9 章的体积优化基础);CommonJS 的 require 是运行时函数调用,依赖图要到运行时才知道,摇不了树。现代前端一律 ESM,Node 也已全面支持,新项目别再写 CommonJS。
JS 只有一个主线程(改 DOM 的前提,不然两个线程同时改 DOM 得打架),靠事件循环调度异步:
console.log("1");
setTimeout(() => console.log("2"), 0); // 宏任务
Promise.resolve().then(() => console.log("3")); // 微任务
console.log("4");
// 输出:1 4 3 2 ← 每次都是,可以背
规则:同步代码跑完(调用栈清空)→ 清空整个微任务队列(Promise.then、await 后续)→ 取一个宏任务(setTimeout、I/O、事件回调)→ 执行它时产生的微任务又立刻清空 → 循环。所以 setTimeout(…, 0) 排在所有已排队微任务之后(输出 3 在 2 前),「0 毫秒」的意思是「下一个宏任务轮次」而非「立即」。
这套模型解释三件事:为什么 JS 里没有真正的并行计算(Web Worker 才是多线程,靠消息通信不共享内存,操作系统课的线程模型在浏览器里的折中);为什么长时间同步代码会卡死页面(主线程被占,事件循环转不动,渲染也被挤掉,第 6 章长任务);为什么「先渲染再执行回调」要用 requestAnimationFrame/宏任务拆分。F12 Performance 面板里那段 Main 线程的火焰图,就是事件循环的现场直播。
for + await 是逐个执行;要并发先 map 成 Promise 数组再 Promise.all。