ES6 语法速查表 - ES6+ 常用语法与 JavaScript 新特性大全
这套速查给想摆脱 var 与回调地狱、改用现代写法的 JavaScript 开发者,覆盖 ES2015+ 里重塑日常编码的那批特性:let/const 块级作用域、箭头函数、解构、模板字符串、Promise/async 流程、import/export 模块、类、Map/Set 和迭代器。与干巴巴的规范条目不同,这里按"这个特性解决什么场景的问题"来分——作用域混乱、回调嵌套、对象取值啰嗦都有对应。读完应能把 var 换成块级绑定、用 async/await 捋平嵌套回调,并用 import/export 拆出多文件项目。
典型使用场景
面向需要把 ES6 及后续语法真正吃透的 JavaScript 开发者。这张表按「变量与函数 → 解构赋值 → 异步编程 → 模块化 → 类与数据结构 → Proxy/Reflect 与 Generator」组织,把容易混淆的语义点讲清:const 锁的是绑定而不是内容、箭头函数没有自己的 this、?. 与 ?? 在 null/undefined 上的短路规则、Promise.all 与 allSettled 在失败处理上的本质差异。数据结构部分对比 Map/Set 与 WeakMap 的键约束和引用关系,Proxy 配合 Reflect 的默认行为转发是理解 Vue 3 响应式的基础,最后的排障条目覆盖了 await 不生效、解构丢值、循环里用 var 导致闭包共享等问题。
变量与函数 9
let x = 1const PI = 3.14const fn = (x) => x * 2const fn = (x, y = 1) => x + yconst fn = (...args) => argsfn(...[1, 2, 3])user?.namevalue ?? "default"`Hello ${name}`解构赋值 5
const [a, b] = [1, 2]const { name, age } = userconst { name: userName } = userconst [a, ...rest] = [1, 2, 3]function fn({ name, age = 18 }) {}异步编程 8
new Promise((resolve, reject) => {})promise.then().catch().finally()Promise.all([p1, p2])Promise.race([p1, p2])async function fn() { await promise }try { await fn() } catch (e) {}Promise.allSettled([p1, p2])Promise.any([p1, p2])模块化 6
export const fn = () => {}export default fnimport { fn } from "./module"import fn from "./module"import * as mod from "./module"import("./module").then(mod => {})类与数据结构 8
class User { constructor() {} }class Admin extends User {}const map = new Map()const set = new Set([1, 2, 3])for (const [k, v] of map) {}Array.from(set)Symbol("id")for (const item of iterable) {}Proxy/Reflect 与 Generator 8
new Proxy(target, handler)handler.get(target, key, receiver)handler.set(target, key, value)Reflect.get(obj, key)function* gen() { yield 1; yield 2; }const it = gen(); it.next(); it.next()for (const v of gen()) {}const wm = new WeakMap()参数矩阵
| 参数 | 作用 | 示例 |
|---|---|---|
let x = 1 | 块级作用域变量,可在块内重新赋值 | let x = 1 |
const PI = 3.14 | 块级作用域常量,绑定不可重新赋值 | const PI = 3.14 |
(x) => x * 2 | 箭头函数,单行表达式体隐式返回且不绑定 this | const fn = (x) => x * 2 |
(x, y = 1) => ... | 默认参数,仅未传或传 undefined 时生效 | const fn = (x, y = 1) => x + y |
(...args) => args | 剩余参数,把多余实参收进真数组 | const fn = (...args) => args |
fn(...[1, 2, 3]) | 展开运算符,摊开可迭代对象;也用于浅拷贝 | fn(...[1, 2, 3]) |
user?.name | 可选链,链上任一节点为 null/undefined 时整体返回 undefined | user?.name |
value ?? "default" | 空值合并,只在 null/undefined 时取默认值 | value ?? "default" |
`Hello ${name}` | 模板字符串插值,支持多行 | `Hello ${name}` |
const [a, b] = [1, 2] | 数组解构,按位置解包 | const [a, b] = [1, 2] |
const { name, age } = user | 对象解构,按属性名抽字段 | const { name, age } = user |
const { name: userName } = user | 解构重命名,把属性绑到新变量名 | const { name: userName } = user |
const [a, ...rest] = [1, 2, 3] | 解构 + 剩余,rest 收集其余元素为数组 | const [a, ...rest] = [1, 2, 3] |
function fn({ name, age = 18 }) {} | 参数解构,形参直接解对象并带默认值 | function fn({ name, age = 18 }) {} |
new Promise((resolve, reject) => {}) | 创建 Promise,完成调 resolve,出错调 reject | new Promise((resolve, reject) => {}) |
.then().catch().finally() | 链式处理成功、异常与收尾 | promise.then().catch().finally() |
Promise.all([p1, p2]) | 并行等待全部,任一 reject 则整体 reject | Promise.all([p1, p2]) |
Promise.allSettled([p1, p2]) | 等待全部落定,逐个返回成功或失败结果 | Promise.allSettled([p1, p2]) |
Promise.race([p1, p2]) | 取最先落定的一个,常用于超时兜底 | Promise.race([p1, p2]) |
Promise.any([p1, p2]) | 取第一个成功的,全部失败才 reject | Promise.any([p1, p2]) |
async function fn() { await promise } | async/await 语法糖,await 等到 Promise 落定 | async function fn() { await promise } |
try { await fn() } catch (e) {} | 用 try/catch 捕获 await 抛出的 reject | try { await fn() } catch (e) {} |
export const fn | 命名导出,导入时按同名引入 | export const fn = () => {} |
export default fn | 默认导出,每模块至多一个,导入可自定名 | export default fn |
import { fn } from "./module" | 命名导入 | import { fn } from "./module" |
import * as mod from "./module" | 把整个模块导入为命名空间对象 | import * as mod from "./module" |
import("./module").then(mod => {}) | 动态导入,运行时按需加载,利于代码分割 | import("./module").then(mod => {}) |
class User { constructor() {} } | 类定义语法 | class User { constructor() {} } |
class Admin extends User {} | 继承父类 | class Admin extends User {} |
new Map() | 键可为对象的映射结构 | const map = new Map() |
new Set([1, 2, 3]) | 自动去重的集合 | const set = new Set([1, 2, 3]) |
for (const [k, v] of map) {} | 按迭代器协议遍历 Map 的键值对 | for (const [k, v] of map) {} |
Array.from(set) | 把可迭代对象转为数组 | Array.from(set) |
Symbol("id") | 生成唯一标识符,避免键名冲突 | Symbol("id") |
new WeakMap() | 键必须为对象的弱引用映射,不阻止垃圾回收 | const wm = new WeakMap() |
new Proxy(target, handler) | 创建代理拦截属性操作 | new Proxy(target, handler) |
handler.get(target, key, receiver) | 拦截属性读取 | handler.get(target, key, receiver) |
handler.set(target, key, value) | 拦截属性设置 | handler.set(target, key, value) |
Reflect.get(obj, key) | 转发默认行为,避免手写重复逻辑 | Reflect.get(obj, key) |
function* gen() { yield 1 } | 生成器函数,yield 处暂停并可返回值 | function* gen() { yield 1; yield 2; } |
it.next() | 手动推进生成器执行 | const it = gen(); it.next(); it.next() |
for (const v of gen()) {} | 用 for...of 消费生成器 | for (const v of gen()) {} |
易错点与避坑指南
现象在 async 函数里写了 await,但后面的代码还是立刻执行了。
原因调用方没有 await 或 .then 接收返回的 Promise,函数体在 await 处挂起后就没人继续,异步链断在了这一层。
处置让整条调用链都串上 await;或用 try { await fn() } catch (e) {} 包住,把 Promise 显式等待落定。
现象用 Promise.all 聚合多个请求,结果一个接口偶尔失败就导致整批数据丢弃。
原因Promise.all 是「全有或全无」语义,任一 reject 会让整体立刻 reject。
处置改用 Promise.allSettled([p1, p2]),等全部落定后再遍历结果,按各自的 status 分支处理。
现象循环里注册的事件回调打印出的 index 全是最后一个值。
原因用 var 声明循环变量时没有块级作用域,所有闭包共享同一个变量。
处置把循环变量改成 let,块级作用域会让每次迭代各自持有绑定。
现象把用户输入的 0 或空串当成「没填」,走了默认值分支。
原因用了 || 做兜底,而 0、""、false 都是假值会被一并替换掉。
处置改用空值合并 value ?? "default",它只在 null 和 undefined 时才取默认值。
现象把对象方法传给回调后 this 变成 undefined,报读属性错误。
原因箭头函数不绑定自己的 this,继承的是定义处的外层 this;普通函数被单独调用时 this 会丢。
处置需要动态 this 时用普通函数并显式传入上下文,或用箭头函数但保证外层作用域就是期望的 this。
现象const 声明的数组 push 之后被判定为「常量被修改」而困惑。
原因const 锁定的是变量与属性的绑定关系,数组内容本身仍是可变的引用。
处置确实需要不可变语义时重新赋值整个变量(const arr = [...arr, item]),而不是调用 mutating 方法。
现象解构取值时报「Cannot destructure property of undefined」。
原因数据是异步拿到的,解构执行时值还是 undefined;或对可能为空的嵌套直接解构。
处置先做空值兜底再解构,或给函数参数默认值:function fn({ name, age = 18 } = {}) {}。
现象用 WeakMap 当普通缓存,写入基本类型作为键时抛错。
原因WeakMap 的键必须是对象,基本类型会被直接拒绝。
处置需要以任意值作键时改用 Map;只有在希望键被垃圾回收时才用 WeakMap。
排障路径
1确认一个值到底是 null 还是 undefined
console.log(user?.name)可选链任一环节为 null/undefined 都会短路返回 undefined,可据此判断断链位置。
2观察 Promise 的最终状态
promise.then().catch().finally()三个环节分别打印成功值、错误与收尾信息,可定位是 resolve 值不对还是 reject 被吞了。
3验证多个异步任务的全部落定结果
Promise.allSettled([p1, p2])返回数组中每项都带 status 字段,能看清哪个成功、哪个失败以及失败原因。
4用 race 实现超时兜底
Promise.race([p1, p2])把一个延时 reject 的 Promise 放进 race,超时的那一路会先落定从而结束等待。
5检查模块到底导出了什么
import * as mod from "./module"把命名空间对象打出来即可看到全部导出项,确认命名导入的名字是否拼写一致。
6手动推进生成器观察暂停与恢复
const it = gen(); it.next(); it.next()每次 next() 返回 { value, done },可以清楚看到 yield 处暂停和恢复执行的过程。
命令示例
解构 + 默认参数 + 剩余参数 + 展开
function log(label, { name, age = 18 } = {}, ...tags) {
console.log(label, name, age, tags)
}
const [a, b] = [1, 2]
const { name: userName } = { name: "tom" }
log("hi", { name: "amy" }, "x", "y")对象参数给 = {} 兜底后,调用方完全不传第二个参数也不会在解构处抛 TypeError;age 的默认值只在传 undefined(缺省)时生效,传 null 会照原值进入。
可选链 + 空值合并处理深层配置
const city = user?.address?.city ?? "unknown"
const items = cart?.items?.length ?? 0
const text = `Hi ${name}, you have ${items} items`用 || 写默认值会把 0 和空串一起替换掉:cart.items.length || "未知" 在空购物车上会显示「未知」。?? 只对 null/undefined 生效,语义上才是默认值。
并发请求:allSettled 收集全部结果并区分失败
const results = await Promise.allSettled([fetchUser(id), fetchOrders(id), fetchStats(id)])
const ok = results.filter(r => r.status === "fulfilled").map(r => r.value)
const failed = results.filter(r => r.status === "rejected")Promise.all 只要一个失败就整体 reject,其余请求仍在后台跑且结果被丢弃;allSettled 等全部落定并逐个给出状态,才能在部分失败时降级展示。
async/await + try/catch 串行执行并逐个处理失败
async function bootstrap() {
try {
const user = await loadUser()
const orders = await loadOrders(user.id)
render({ user, orders })
} catch (e) {
console.error("failed:", e.message)
} finally {
hideSpinner()
}
}顺序 await 天然串行且每个 await 都能单独 try/catch;若换成 Promise.all,就失去这种逐步降级能力,只能整体兜底。finally 保证加载态必定收尾。
Map / Set 遍历与生成器消费
const map = new Map([["a", 1], ["b", 2]])
for (const [k, v] of map) {
console.log(k, v)
}
const set = new Set([1, 2, 2, 3])
const arr = Array.from(set)
function* ids() {
yield 1
yield 2
}
for (const v of ids()) {
console.log(v)
}Set 天然去重,Array.from 后即可当普通数组用;生成器不产生中间数组,产出无限序列时也只占常数内存,for...of 会在自然耗尽时退出。
常见坑与注意事项
- ?? 不能与 || 或 && 直接混写:a ?? b || c 是 SyntaxError,必须写成 (a ?? b) || c,否则整个模块在解析阶段就报错,控制台常只指向 import 语句。
- 对 null/undefined 解构会直接抛 TypeError:const { name } = user 在 user 为空时炸掉,接口返回缺字段的字段名尤其常见;给解构对象本身加 = {} 兜底。
- Promise.all 在首个 reject 时立刻 reject,其余请求并没有被取消,仍会消耗连接与带宽;对「部分失败也要展示」的场景必须换 allSettled 或逐个 .catch。
- 箭头函数没有自己的 this:写在对象方法或 class 里时 this 指向外层作用域(常见是 window/undefined),事件回调里 this 失效属于同一类问题,改用 function 或显式 bind。
- Node 与浏览器对 ESM 的解析规则不同:Node 要求完整文件扩展名,浏览器与多数打包器默认不写;同一个 import 路径换环境就报 ERR_MODULE_NOT_FOUND 或 404。
- 对象展开 {...obj} 是 ES2018 才有的语法;把文件交给只支持 ES6 的转译目标(Babel 只做语法转换、不补内置方法)时,Object.entries/Array.flat 会静默不存在而不报错。
提示
- const 声明的对象/数组内容仍可修改,只是引用不可变。
- 箭头函数没有自己的 this,继承外层作用域。
- async/await 是 Promise 的语法糖,错误用 try/catch 捕获。
- ?. 和 ?? 是 ES2020 特性,?. 短路返回 undefined,?? 仅在 null/undefined 时触发。
- Proxy 常用于响应式系统(如 Vue 3 reactive),Reflect 提供对应的默认行为转发。
- Generator 函数执行到 yield 暂停并返回值,调用 next() 恢复执行。
- WeakMap/WeakSet 的键是弱引用,不影响垃圾回收,适合缓存场景。
常见问题
const 声明的对象还能不能改?为什么「常量」却不是只读?
const 锁的是绑定,不是值本身。const obj = { a: 1 } 之后 obj.a = 2 完全合法,因为对象的属性仍可写;真正被禁止的是再次赋值 obj = {...},那会触发 TypeError。数组同理:const list = [1]; list.push(2) 可以,list = [] 会报错。所以写法上默认用 const,只有确实需要重新赋值的变量才用 let,这样能表达意图;而 Map、Set 的增删改方法(set、add、delete)也属于「改内容」,在 const 声明的对象上是允许的。
?? 和 || 在取默认值时到底差在哪?
差别只在哪些假值会被替换。|| 对所有假值生效,包括 0、""、false 和 NaN;?? 只在 null 和 undefined 时才取右边。例如 count ?? 0 会正确保留 0,而 count || 0 会把 0 也换掉,这在「计数为 0」和「空字符串是合法输入」的场景会造成隐性 bug。配套的 ?. 是可选链:user?.name 在 user 为 null/undefined 时整条链短路返回 undefined,不再需要层层 user && user.name。
Promise.all 和 Promise.allSettled 在批量请求上该怎么选?
关键看「一个失败是否要让整体失败」。Promise.all 并行发起全部请求,但任一 reject 就会让整体 reject,此时其它请求的结果全部拿不到,适合「全成功才算成功」的强一致场景。Promise.allSettled 会等所有 Promise 都落定,再返回一组带 status 的结果,逐个标明 fulfilled 或 rejected,失败项带着 reason,一个失败不影响其它,适合批量健康检查、多接口聚合看板这类允许部分成功的场景。只想取第一个成功结果时用 Promise.any,全部失败才 reject。
箭头函数为什么不能直接当对象方法用?
因为箭头函数不绑定自己的 this,它继承定义时外层作用域的 this。写成 obj.handle = () => this 里的 this 指向模块或外层作用域,而不是 obj;同理,箭头函数也没有 arguments,用不了类方法依赖的动态 this。对策是:需要动态 this 时用普通函数 handle() {}(挂到对象上调用时 this 就是 obj),或者在类里用方法简写 handle() {};只有在「确实想捕获外层 this」的回调里才用箭头函数。ES6 的类字段 handle = () => {} 是箭头,但把定义与使用绑在同一条赋值链上,此时 this 才是实例。
动态 import() 和静态 import 在打包结果上差在哪?
静态的 import { fn } from "./module" 会被打包器直接判定为依赖,在构建阶段就打进同一个 chunk,无论运行时是否真的用到。import("./module").then(mod => {}) 返回一个 Promise,模块在运行时按需加载,因而可以被单独切分成独立 chunk,只有真正触发这次调用时才下载——这就是代码分割的基本手段。代价是调用方必须处理异步,模块导出的东西要从 then 回调的 mod 参数上取(如 mod.fn()),不能像同步导入那样直接用。另外 import * as mod from "./module" 是把整个模块绑定成一个命名空间对象,适合需要批量转发的场景。
由 巧匠 维护
公开更新于 2026年7月21日,内容持续校对官方文档。