ES6 语法速查表 - ES6+ 常用语法与 JavaScript 新特性大全

这套速查给想摆脱 var 与回调地狱、改用现代写法的 JavaScript 开发者,覆盖 ES2015+ 里重塑日常编码的那批特性:let/const 块级作用域、箭头函数、解构、模板字符串、Promise/async 流程、import/export 模块、类、Map/Set 和迭代器。与干巴巴的规范条目不同,这里按"这个特性解决什么场景的问题"来分——作用域混乱、回调嵌套、对象取值啰嗦都有对应。读完应能把 var 换成块级绑定、用 async/await 捋平嵌套回调,并用 import/export 拆出多文件项目。

编程语言·共 44 条命令·最后更新 2026-07-21

典型使用场景

面向需要把 ES6 及后续语法真正吃透的 JavaScript 开发者。这张表按「变量与函数 → 解构赋值 → 异步编程 → 模块化 → 类与数据结构 → Proxy/Reflect 与 Generator」组织,把容易混淆的语义点讲清:const 锁的是绑定而不是内容、箭头函数没有自己的 this、?. 与 ?? 在 null/undefined 上的短路规则、Promise.all 与 allSettled 在失败处理上的本质差异。数据结构部分对比 Map/Set 与 WeakMap 的键约束和引用关系,Proxy 配合 Reflect 的默认行为转发是理解 Vue 3 响应式的基础,最后的排障条目覆盖了 await 不生效、解构丢值、循环里用 var 导致闭包共享等问题。

变量与函数 9

let x = 1
声明块级作用域变量,可在最近的大括号块内重新赋值,不再有 var 的提升问题
const PI = 3.14
声明块级作用域常量,赋值后不能重新绑定,缺省首选用 const
const fn = (x) => x * 2
箭头函数,单行表达式体隐式 return,且不绑定自己的 this
const fn = (x, y = 1) => x + y
默认参数:未传或传 undefined 时才用 y = 1,不必再在函数体里判空
const fn = (...args) => args
剩余参数:把调用时多出的实参收进真数组 args
fn(...[1, 2, 3])
展开运算符:把一个可迭代对象原地摊开作为实参,也用于数组/对象浅拷贝
user?.name
可选链:路径上任一节点为 null/undefined 就整体短路返回 undefined,取代冗长的 && 判断
value ?? "default"
空值合并:仅当 value 为 null/undefined 时取默认值,不会把 0 或空串也替换掉
`Hello ${name}`
模板字符串:用 ${} 插值,支持多行,替代 + 拼串

解构赋值 5

const [a, b] = [1, 2]
数组解构:按位置把元素解包给变量
const { name, age } = user
对象解构:按属性名抽字段,避免反复 user.name 取值
const { name: userName } = user
解构重命名:把 user.name 的值绑到新变量 userName
const [a, ...rest] = [1, 2, 3]
解构 + 剩余:a 取首项,rest 收其余为数组
function fn({ name, age = 18 }) {}
函数参数解构:形参直接解构对象,age 缺省为 18

异步编程 8

new Promise((resolve, reject) => {})
创建 Promise:执行完成调 resolve,出错调 reject
promise.then().catch().finally()
链式调用:then 取成功值、catch 拦异常、finally 无论结果都会执行
Promise.all([p1, p2])
并行执行全部,任一 reject 则整体 reject,结果按传入顺序聚合
Promise.race([p1, p2])
取最先落定(成功或失败)的那个,做超时兜底常用
async function fn() { await promise }
async/await:让异步代码看起来像同步,await 暂停直到 promise 落定
try { await fn() } catch (e) {}
错误处理:用 try/catch 拦下 await 抛出的 reject 错误
Promise.allSettled([p1, p2])
等待全部落定并逐个返回成功/失败结果,一个失败不影响其它
Promise.any([p1, p2])
取第一个成功的,全部失败才 reject(ES2021)

模块化 6

export const fn = () => {}
命名导出:import 时按同名变量引入
export default fn
默认导出:每个模块至多一个,import 时可自定义名字
import { fn } from "./module"
命名导入:按导出的名字 {} 引入
import fn from "./module"
默认导入:引入默认导出并起本地名
import * as mod from "./module"
导入整个模块为一个命名空间对象 mod
import("./module").then(mod => {})
动态导入:运行时按需加载并在 Promise 落定后使用,利于代码分割

类与数据结构 8

class User { constructor() {} }
类定义
class Admin extends User {}
继承
const map = new Map()
Map(键可以是对象)
const set = new Set([1, 2, 3])
Set(去重)
for (const [k, v] of map) {}
遍历 Map
Array.from(set)
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)
反射 API(转发默认行为)
function* gen() { yield 1; yield 2; }
生成器函数
const it = gen(); it.next(); it.next()
手动迭代生成器
for (const v of gen()) {}
用 for...of 消费生成器
const wm = new WeakMap()
WeakMap(键必须为对象,弱引用)

参数矩阵

参数作用示例
let x = 1块级作用域变量,可在块内重新赋值let x = 1
const PI = 3.14块级作用域常量,绑定不可重新赋值const PI = 3.14
(x) => x * 2箭头函数,单行表达式体隐式返回且不绑定 thisconst 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 时整体返回 undefineduser?.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,出错调 rejectnew Promise((resolve, reject) => {})
.then().catch().finally()链式处理成功、异常与收尾promise.then().catch().finally()
Promise.all([p1, p2])并行等待全部,任一 reject 则整体 rejectPromise.all([p1, p2])
Promise.allSettled([p1, p2])等待全部落定,逐个返回成功或失败结果Promise.allSettled([p1, p2])
Promise.race([p1, p2])取最先落定的一个,常用于超时兜底Promise.race([p1, p2])
Promise.any([p1, p2])取第一个成功的,全部失败才 rejectPromise.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 抛出的 rejecttry { 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. 1确认一个值到底是 null 还是 undefined

    console.log(user?.name)

    可选链任一环节为 null/undefined 都会短路返回 undefined,可据此判断断链位置。

  2. 2观察 Promise 的最终状态

    promise.then().catch().finally()

    三个环节分别打印成功值、错误与收尾信息,可定位是 resolve 值不对还是 reject 被吞了。

  3. 3验证多个异步任务的全部落定结果

    Promise.allSettled([p1, p2])

    返回数组中每项都带 status 字段,能看清哪个成功、哪个失败以及失败原因。

  4. 4用 race 实现超时兜底

    Promise.race([p1, p2])

    把一个延时 reject 的 Promise 放进 race,超时的那一路会先落定从而结束等待。

  5. 5检查模块到底导出了什么

    import * as mod from "./module"

    把命名空间对象打出来即可看到全部导出项,确认命名导入的名字是否拼写一致。

  6. 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日,内容持续校对官方文档。

联系我们

命令或描述有误?提交反馈、商务合作或产品建议都可发送邮件给我们。

联系我们