"熟悉 ES6+ 语法"不等于"背过解构和箭头函数"。很多人天天写 const、=>、async/await,但遇到解构默认值、展开的浅拷贝、模块循环依赖、Promise 错误处理、可选链的边界就说不清楚。
一句话判断标准:
给你一段 ES6+ 代码,你能说出它等价的老写法、背后的设计意图、以及什么时候不该用。
ES6+ 范围很广,不需要全部精通,但要分清必须熟练 、理解原理 、知道即可三个层次。
一、先建立知识地图:ES6+ 到底包含什么
| 层次 | 内容 | 重要程度 |
|--------------|--------------------------------------------------------------------------------------|------|------------------------------------------------------------------|------|
| 日常高频 | let/const、箭头函数、模板字符串、解构、展开、默认参数、可选链、空值合并 | 必须熟练 |
| 异步 | Promise、async/await、Promise.all/allSettled/race/any | 必须熟练 |
| 模块化 | ESM 的 import/export、动态 import()、默认导出 vs 具名导出 | 必须熟练 |
| 数组/对象新方法 | map/filter/reduce/find/flat/flatMap、Object.entries/fromEntries/hasOwn、Array.at | 必须熟练 |
| 类与继承 | class、extends、super、静态方法、私有字段 # | 理解原理 |
| 迭代与生成器 | for...of、迭代器协议、生成器 function*、yield | 理解原理 |
| 元编程 | Proxy、Reflect、Symbol | 理解原理 |
| 新数据结构 | Map、Set、WeakMap、WeakSet | 理解原理 |
| 现代新增 | 逻辑赋值 ??=/` | | =/&&=、structuredClone、顶层 await、Array.prototype.group` | 知道即可 |
| 提案阶段 | 装饰器、using、管道运算符 | 知道即可 |
其中解构、展开、箭头函数、Promise、async/await、模块化是分水岭,也是"会用"和"熟悉"的分界线。
二、逐层突破:每层怎么学、学到什么程度
1. let/const:块级作用域与暂时性死区
let/const是块级作用域,var是函数作用域。- 暂时性死区(TDZ) :在声明前访问会报错,不是
undefined。 const只保证绑定不变,对象内部属性仍可修改。- 循环中
let每次迭代创建新绑定,var共享一个。
学到什么程度 :能解释 for (var i = 0; i < 3; i++) setTimeout(() => console.log(i)) 输出三个 3,而 let 输出 0、1、2。
2. 箭头函数:不只是语法糖
this继承外层 ,不参与call/apply/bind绑定。- 没有
arguments,用剩余参数...args替代。 - 不能作为构造函数 ,没有
prototype。 - 不能用作生成器。
- 适合回调、短函数;不适合对象方法、需要动态
this的场景。
学到什么程度:能说出至少三个"不该用箭头函数"的场景,并解释原因。
3. 模板字符串:不只是拼接
- 多行字符串、插值
${}。 - 标签模板 :
tag\...``,用于 i18n、SQL 防注入、样式组件。 - 嵌套和表达式。
学到什么程度 :能写一个简单的标签模板函数,理解它接收 strings 和 values 两个参数。
4. 解构:日常最高频,也最容易踩坑
- 数组解构:按位置、跳过、剩余、默认值、交换变量。
- 对象解构 :按属性名、重命名
{ a: b }、默认值、嵌套、剩余。 - 函数参数解构 :
function fn({ a, b = 1 } = {})。 - 坑点 :
- 默认值只在
undefined时生效,null不生效。 - 解构
null/undefined会报错,需要默认值兜底。 - 嵌套解构层级深时可读性差。
- 默认值只在
js
const { a = 1, b: { c } = {} } = obj ?? {}
学到什么程度 :能解释 const { a = 1 } = { a: null } 的结果是 null 不是 1,能写出安全的嵌套解构。
5. 展开与剩余:浅拷贝的真相
- 展开 :数组
[...arr]、对象{ ...obj }、函数调用fn(...args)。 - 剩余 :
function fn(...args)、const [first, ...rest] = arr、const { a, ...rest } = obj。 - 关键认知 :展开是浅拷贝,嵌套对象仍是引用。
- 对象展开的顺序:后面的覆盖前面的。
- 展开 vs
Object.assign:行为类似,展开更简洁。
学到什么程度 :能解释 const b = { ...a } 后修改 b.nested.x 为什么会影响 a,能说出深拷贝的正确做法(structuredClone)。
6. 默认参数:只在 undefined 时生效
- 默认参数在调用时求值,不是定义时。
- 只在参数为
undefined时生效,null、0、''不触发。 - 默认参数可以引用前面的参数。
- 默认参数与解构结合:
function fn({ a = 1 } = {})。
学到什么程度 :能解释 fn(null) 和 fn(undefined) 的区别。
7. 可选链与空值合并:安全访问的边界
- 可选链
?.:obj?.a?.b、arr?.[0]、fn?.()。 - 空值合并
??:只在null/undefined时取右侧,0、''、false不触发。 ??vs||:||会把0、''、false当成假值,??不会。- 不能与
&&/||混用不加括号。 - 可选链的边界 :只短路访问,不短路赋值;
obj?.a = 1是语法错误。
学到什么程度 :能解释 0 ?? 1 是 0,而 0 || 1 是 1,能说出可选链不能用在哪些地方。
8. Promise:异步的基石
- 三种状态 :
pending、fulfilled、rejected,状态不可逆。 - 链式调用 :
then返回新 Promise,catch捕获前面所有错误,finally无论成败都执行。 - 静态方法 :
Promise.all:全部成功才成功,一个失败就失败。Promise.allSettled:等所有完成,返回每个的结果。Promise.race:第一个完成的结果。Promise.any:第一个成功的,全部失败才失败。
- 常见坑 :忘记
return导致链断裂、catch后没有重新抛出、在then里抛错不会被外层try/catch捕获。
学到什么程度 :能手写一个简易 Promise,能说出 Promise.all 和 allSettled 的使用场景差异。
9. async/await:Promise 的语法糖
async函数总是返回 Promise。await等待 Promise 完成,后面的代码相当于then回调。- 错误处理 :
try/catch捕获await的 rejection。 - 并发 vs 串行 :
await a(); await b()是串行,await Promise.all([a(), b()])是并发。 - 常见坑 :在循环里
await导致串行、忘记try/catch、在forEach里用async不会等待。
学到什么程度 :能把一段 then 链改写成 async/await,能说出循环里并发请求的正确写法。
10. 模块化:ESM 的静态特性
- 具名导出 vs 默认导出 :
export const a、export default。 - 导入 :
import { a } from、import * as ns、import defaultExport。 - 重命名 :
import { a as b }、export { a as b }。 - 动态导入 :
import()返回 Promise,用于懒加载。 - ESM vs CommonJS :静态 vs 动态、值引用 vs 值拷贝、
import提升。 - 常见坑 :循环依赖、默认导出和具名导出混用、
import必须在顶层。
学到什么程度 :能解释 ESM 的 import 为什么会被提升,能说出循环依赖时 ESM 和 CommonJS 的行为差异。
11. 类与继承:理解 class 的本质
class是原型继承的语法糖。constructor、实例方法、静态方法、getter/setter。extends、super、方法重写。- 私有字段
#:真正的私有,不是约定。 - 静态块 :
static { ... }。 - 常见坑 :
class不会提升、方法不可枚举、this丢失。
学到什么程度 :能手写一个 class 继承,能解释 super() 必须在 this 之前调用。
12. 迭代器与生成器:理解 for...of
- 迭代器协议 :
next()返回{ value, done }。 - 可迭代协议 :实现
Symbol.iterator。 - 生成器 :
function*、yield、yield*、return、throw。 - 应用 :惰性求值、无限序列、异步流程控制(
co库)。 for...of的适用范围 :数组、字符串、Map、Set、arguments、NodeList。
学到什么程度:能写一个自定义可迭代对象,能用生成器实现一个无限序列。
13. Map/Set/WeakMap/WeakSet
Mapvs 对象 :键可以是任意类型、有序、size、迭代。Set:去重、交集/并集/差集。WeakMap/WeakSet:弱引用,键必须是对象,不可迭代,用于避免内存泄漏。- 使用场景:缓存、私有数据、DOM 关联数据。
学到什么程度 :能说出 WeakMap 为什么能避免内存泄漏,能用 Set 实现数组去重和集合运算。
14. Proxy/Reflect/Symbol
Proxy:拦截对象操作(get/set/has/deleteProperty等),Vue3 响应式的底层。Reflect:与Proxy配合,提供对象操作的默认行为。Symbol:唯一值、Symbol.iterator、Symbol.toStringTag。- 学到什么程度 :能用
Proxy写一个简单的响应式或校验器。
15. 现代新增:知道即可,但要用得自然
- 逻辑赋值 :
a ??= b、a ||= b、a &&= b。 structuredClone:现代深拷贝,支持循环引用、Date、Map/Set。Object.hasOwn:替代Object.prototype.hasOwnProperty.call。Array.prototype.at:负索引访问。- 顶层
await:ESM 模块中直接await。 Array.prototype.group:按条件分组。findLast/findLastIndex:从后往前找。
三、学习方法:读文档、写等价代码、做输出题、读源码
1. 读 MDN,而不是只读博客
MDN 的每个方法页面都有"语法""参数""返回值""异常""示例""浏览器兼容"。遇到不确定的行为,以 MDN 和 ECMAScript 规范为准。
2. 写"新旧对照"
每学一个 ES6+ 特性,写一遍老写法和新写法:
js
// 老写法
var self = this
var arr = [1, 2, 3]
var first = arr[0]
var rest = arr.slice(1)
// 新写法
const first = arr[0]
const [, ...rest] = arr
对照能让你理解语法糖背后的机制,也能在维护老代码时快速转换。
3. 做输出题,训练预判
先预测输出,再运行验证。重点练:
- 解构默认值与
null/undefined - 展开的浅拷贝
- 箭头函数的
this Promise+async/await的执行顺序- 可选链和
??的边界 - 模块循环依赖
4. 读源码,看真实用法
- 读
lodash的debounce/throttle,看闭包和剩余参数。 - 读
axios的拦截器,看 Promise 链。 - 读 Vue3 的
reactive,看Proxy/Reflect。 - 读
vue-router的守卫,看async/await。
5. 用新语法重构旧代码
拿自己项目里的老代码,用 ES6+ 重构:
var→const/letfunction回调 → 箭头函数- 字符串拼接 → 模板字符串
arguments→ 剩余参数then链 →async/awaitObject.assign→ 展开- 多层
if判断 → 可选链 +??
重构时问自己:新写法是否更清晰?有没有引入坑?
四、可操作的 30 天练习清单
| 周 | 任务 | 产出 |
|---|---|---|
| 第 1 周 | 通读 MDN 的 let/const、箭头函数、解构、展开、默认参数、可选链、??;做 20 道输出题 |
一份语法速查笔记 + 输出题答案 |
| 第 2 周 | 系统学习 Promise 和 async/await;手写简易 Promise;把项目里的 then 链改写成 async/await |
Promise 实现 + 重构记录 |
| 第 3 周 | 学习 ESM、class、迭代器/生成器、Map/Set/WeakMap;写一个自定义可迭代对象 |
模块化笔记 + 可迭代对象 demo |
| 第 4 周 | 学习 Proxy/Reflect/Symbol;用 Proxy 写一个响应式;用新语法重构一个旧模块 |
响应式 demo + 重构前后对比 |
五、常见误区
- "天天写
const/=>就是熟悉 ES6+" :语法会用不等于理解机制,解构默认值、展开浅拷贝、this绑定才是分水岭。 - "箭头函数能替代所有
function" :对象方法、构造函数、需要arguments的场景不能用箭头函数。 - "展开是深拷贝":展开只复制第一层,嵌套对象仍是引用。
- "
??和||一样" :??只在null/undefined时取右侧,||会把0、''、false当假值。 - "
async/await比 Promise 好,全用async/await" :并发场景需要Promise.all,串行await会变慢。 - "ESM 和 CommonJS 差不多":静态 vs 动态、值引用 vs 值拷贝、循环依赖行为都不同。
- "
class就是新语法,和原型没关系" :class是原型继承的语法糖,理解原型才能理解class。 - "
WeakMap和Map一样用" :WeakMap键必须是对象、不可迭代,但能避免内存泄漏。
六、怎么验证自己熟悉了
能全部做到,说明 ES6+ 已经熟悉:
- 能解释
for (var i...)和for (let i...)在setTimeout里的输出差异。 - 能说出至少三个不该用箭头函数的场景。
- 能解释
const { a = 1 } = { a: null }的结果是null。 - 能解释
{ ...obj }是浅拷贝,并说出深拷贝的正确做法。 - 能解释
0 ?? 1和0 || 1的区别。 - 能手写一个简易 Promise,能说出
Promise.all和allSettled的差异。 - 能把
then链改写成async/await,并正确处理并发和错误。 - 能解释 ESM 的
import提升和循环依赖行为。 - 能写一个自定义可迭代对象,能用生成器实现无限序列。
- 能用
Proxy写一个简单的响应式或校验器。 - 能用
WeakMap实现一个不泄漏的缓存。 - 能用
structuredClone做深拷贝,并说出它和JSON.parse(JSON.stringify())的差异。
ES6+ 熟悉的人,写出的代码更简洁、更安全、更现代,读框架和库的源码也更轻松。它不是"背语法",而是理解每个特性为什么存在、什么时候用、什么时候不该用。