Skip to content

ES6+ 核心特性深度解析

解构赋值

解构赋值是 ES6 引入的一种从数组或对象中提取值、对变量进行赋值的语法糖。其本质是模式匹配——只要等号两边的模式相同,左边的变量就会被赋予右边对应的值。引擎在编译阶段会将解构语法转换为一系列赋值语句,因此它是零运行时开销的语法转换。

对象解构

对象解构基于属性名进行匹配,与顺序无关。

js
const person = { name: 'Alice', age: 30, job: 'Engineer' }

const { name, age } = person

const { name: userName, age: userAge } = person

const { name, ...rest } = person

深层原理:对象解构在规范中通过 RequireObjectCoercible 确保右侧值不是 nullundefined,然后对每个解构属性调用 GetV 获取值。这意味着原始类型也可以被解构,因为它们会被临时装箱为包装对象:

js
const { length } = 'hello'

const { toFixed } = 42

数组解构

数组解构基于迭代器协议进行匹配——它会对右侧值调用 Symbol.iterator 方法,然后按顺序取值。

js
const [a, b, c] = [1, 2, 3]

const [first, , third] = [1, 2, 3]

const [head, ...tail] = [1, 2, 3, 4]

由于数组解构依赖迭代器协议,任何实现了 Symbol.iterator 的对象都可以被数组解构:

js
const [a, b, c] = 'abc'

const [x, y] = new Set([10, 20])

function* gen() {
  yield 1
  yield 2
  yield 3
}
const [p, q, r] = gen()

嵌套解构

解构可以嵌套到任意深度,对象与数组可自由组合。

js
const data = {
  users: [
    { id: 1, profile: { name: 'Alice', scores: [90, 85, 92] } },
    { id: 2, profile: { name: 'Bob', scores: [78, 88, 95] } }
  ]
}

const {
  users: [
    {
      profile: {
        name: firstName,
        scores: [bestScore]
      }
    }
  ]
} = data

默认值

当解构的值严格等于 undefined 时,默认值生效。这里的关键是严格等于 undefined——null 不会触发默认值。

js
const { a = 1, b = 2 } = { a: 10, b: undefined }

const { x = 1 } = { x: null }

const [m = 'default', n = 'default'] = [undefined, null]

默认值可以是表达式,且是惰性求值的——只有在需要时才会执行:

js
function expensive() {
  console.log('computed')
  return 42
}

const { val = expensive() } = { val: 100 }

const { val2 = expensive() } = {}

函数参数解构

解构在函数参数中极为常见,特别适合处理配置对象:

js
function createUser({ name, age = 18, role = 'user' } = {}) {
  return { name, age, role }
}

createUser({ name: 'Alice' })
createUser({ name: 'Bob', age: 25, role: 'admin' })
createUser()

展开运算符

展开运算符 ... 在 ES6 中以两种形态出现:展开(Spread)剩余(Rest)。两者语法相同,但语义相反——Spread 是将可迭代对象"展开",Rest 是将多个元素"收集"为一个。

数组展开

数组展开基于迭代器协议,将可迭代对象的每个元素展开到新数组中。

js
const arr1 = [1, 2, 3]
const arr2 = [4, 5, 6]
const merged = [...arr1, ...arr2]

const copy = [...arr1]

展开操作创建的是浅拷贝——嵌套的引用类型仍然共享同一引用:

js
const original = [{ a: 1 }, { b: 2 }]
const cloned = [...original]
cloned[0].a = 999

对象展开

对象展开(ES2018)将源对象的自有可枚举属性逐一复制到目标对象中。

js
const defaults = { theme: 'light', lang: 'en', debug: false }
const userConfig = { theme: 'dark', lang: 'zh' }
const config = { ...defaults, ...userConfig }

const obj = { a: 1, b: 2, c: 3 }
const { a, ...remaining } = obj

对象展开的属性覆盖遵循后者胜出原则,这一机制在源码层面等价于 Object.assign({}, ...sources),但有一个关键区别:展开运算符不会触发目标对象上的 setter。

js
const base = { x: 1 }
const extended = { ...base, x: 2, y: 3 }

函数参数中的展开与剩余

js
function sum(...numbers) {
  return numbers.reduce((acc, n) => acc + n, 0)
}
sum(1, 2, 3, 4)

const args = [1, 2, 3]
Math.max(...args)

Rest 参数在规范中取代了 arguments 对象。与 arguments 的本质区别在于:Rest 参数是真正的 Array 实例,拥有所有数组方法;而 arguments 是一个类数组对象(Array-like),并且在非严格模式下与命名参数存在"别名绑定"的怪异行为。


Promise 与 async/await

此处仅作概要梳理,详细内容请参阅 Promise 专题。

Promise 核心概念

Promise 是对异步操作最终完成(或失败)的表示。它有三种状态:pendingfulfilledrejected,状态一旦变更不可逆转(settled)。

js
const p = new Promise((resolve, reject) => {
  setTimeout(() => resolve('done'), 1000)
})

p.then(val => console.log(val))
 .catch(err => console.error(err))
 .finally(() => console.log('cleanup'))

Promise 的微任务调度机制是其核心——.then 回调被推入微任务队列(microtask queue),在当前宏任务结束后、下一个宏任务开始前执行。

async/await

async/await 是 Generator + Promise 的语法糖,让异步代码以同步形式书写。async 函数始终返回 Promise,await 会暂停函数执行直到 Promise settled。

js
async function fetchData(url) {
  try {
    const response = await fetch(url)
    const data = await response.json()
    return data
  } catch (error) {
    throw new Error(`Fetch failed: ${error.message}`)
  }
}

并发控制

js
const [users, posts] = await Promise.all([
  fetchUsers(),
  fetchPosts()
])

const result = await Promise.allSettled([
  fetchCritical(),
  fetchOptional()
])

const fastest = await Promise.race([
  fetchFromCDN1(),
  fetchFromCDN2()
])

const firstSuccess = await Promise.any([
  fetchFromPrimary(),
  fetchFromFallback()
])

Proxy 与 Reflect

Proxy 是 ES6 引入的元编程(Meta-programming) 能力,允许你定义对象基本操作的自定义行为。在 ECMAScript 规范中,对象的基本操作被定义为一组"内部方法"(Internal Methods),Proxy 正是对这些内部方法的拦截层。

基本用法

Proxy 接受两个参数:target(目标对象)和 handler(拦截器对象),handler 中的方法被称为 trap

js
const target = { name: 'Alice', age: 30 }

const handler = {
  get(target, property, receiver) {
    console.log(`Reading ${property}`)
    return Reflect.get(target, property, receiver)
  },
  set(target, property, value, receiver) {
    console.log(`Writing ${property} = ${value}`)
    return Reflect.set(target, property, value, receiver)
  }
}

const proxy = new Proxy(target, handler)
proxy.name
proxy.age = 31

可拦截操作详解

Proxy 支持 13 种 trap,覆盖了对象的所有内部方法:

Trap触发时机对应内部方法
get读取属性[[Get]]
set设置属性[[Set]]
hasin 操作符[[HasProperty]]
deletePropertydelete 操作符[[Delete]]
ownKeysObject.keys() / for...in[[OwnPropertyKeys]]
getOwnPropertyDescriptorObject.getOwnPropertyDescriptor()[[GetOwnProperty]]
definePropertyObject.defineProperty()[[DefineOwnProperty]]
getPrototypeOfObject.getPrototypeOf()[[GetPrototypeOf]]
setPrototypeOfObject.setPrototypeOf()[[SetPrototypeOf]]
isExtensibleObject.isExtensible()[[IsExtensible]]
preventExtensionsObject.preventExtensions()[[PreventExtensions]]
apply函数调用[[Call]]
constructnew 操作符[[Construct]]
js
const handler = {
  has(target, key) {
    if (key.startsWith('_')) {
      return false
    }
    return Reflect.has(target, key)
  },

  deleteProperty(target, key) {
    if (key.startsWith('_')) {
      throw new Error(`Cannot delete private property "${key}"`)
    }
    return Reflect.deleteProperty(target, key)
  },

  ownKeys(target) {
    return Reflect.ownKeys(target).filter(key => !String(key).startsWith('_'))
  }
}

const obj = new Proxy({ _secret: 42, name: 'public' }, handler)
'_secret' in obj
Object.keys(obj)

Reflect 的设计意义

Reflect 并非简单的工具函数集合,它的设计目标有三个层面:

  1. 提供与 Proxy trap 一一对应的默认行为,让你在 trap 中方便地调用"原始操作"。
  2. 将命令式操作函数化delete obj.keyReflect.deleteProperty(obj, key)key in objReflect.has(obj, key)
  3. 返回布尔值代替抛异常Object.defineProperty 失败时抛异常,Reflect.defineProperty 返回 false
js
Reflect.has({ a: 1 }, 'a')

Reflect.ownKeys({ a: 1, [Symbol('b')]: 2 })

const success = Reflect.defineProperty({}, 'x', { value: 1 })

实际用例:响应式系统

Vue 3 的响应式系统正是基于 Proxy 构建的。以下是核心思路的简化实现:

js
const targetMap = new WeakMap()
let activeEffect = null

function track(target, key) {
  if (!activeEffect) return
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    depsMap = new Map()
    targetMap.set(target, depsMap)
  }
  let dep = depsMap.get(key)
  if (!dep) {
    dep = new Set()
    depsMap.set(key, dep)
  }
  dep.add(activeEffect)
}

function trigger(target, key) {
  const depsMap = targetMap.get(target)
  if (!depsMap) return
  const dep = depsMap.get(key)
  if (dep) {
    dep.forEach(effect => effect())
  }
}

function reactive(obj) {
  return new Proxy(obj, {
    get(target, key, receiver) {
      track(target, key)
      const result = Reflect.get(target, key, receiver)
      if (typeof result === 'object' && result !== null) {
        return reactive(result)
      }
      return result
    },
    set(target, key, value, receiver) {
      const oldValue = target[key]
      const result = Reflect.set(target, key, value, receiver)
      if (oldValue !== value) {
        trigger(target, key)
      }
      return result
    }
  })
}

function effect(fn) {
  activeEffect = fn
  fn()
  activeEffect = null
}

const state = reactive({ count: 0, nested: { value: 1 } })

effect(() => {
  console.log(`count is: ${state.count}`)
})

state.count++

这里使用 Reflect.get 而非直接 target[key] 的关键原因在于 receiver 参数的传递。当目标对象有 getter 且通过原型链继承时,receiver 确保 this 指向正确的代理对象,从而让依赖追踪不会遗漏。

可撤销代理

js
const { proxy, revoke } = Proxy.revocable({ data: 'sensitive' }, {
  get(target, key) {
    return Reflect.get(target, key)
  }
})

proxy.data

revoke()

Symbol

Symbol 是 ES6 引入的第七种原始类型,每个 Symbol 值都是唯一且不可变的。它的核心价值在于提供"绝对不会冲突的属性键",以及作为语言级别的"协议标识符"。

唯一性

js
const s1 = Symbol('description')
const s2 = Symbol('description')
s1 === s2

typeof s1

const key = Symbol('myKey')
const obj = { [key]: 'value' }
obj[key]

Symbol 属性不会出现在 for...inObject.keys()JSON.stringify() 中,只能通过 Object.getOwnPropertySymbols()Reflect.ownKeys() 获取。这一特性使其成为实现"半私有"属性的利器。

Symbol.for 与全局注册表

Symbol.for(key) 通过全局 Symbol 注册表实现跨域、跨 iframe 的 Symbol 共享:

js
const s1 = Symbol.for('app.id')
const s2 = Symbol.for('app.id')
s1 === s2

Symbol.keyFor(s1)
Symbol.keyFor(Symbol('local'))

Well-known Symbols

ECMAScript 规范定义了一组 Well-known Symbols,用于定制语言内置行为。它们是 JavaScript 的"内部协议":

js
class Money {
  constructor(amount, currency) {
    this.amount = amount
    this.currency = currency
  }

  [Symbol.toPrimitive](hint) {
    if (hint === 'number') return this.amount
    if (hint === 'string') return `${this.amount} ${this.currency}`
    return this.amount
  }
}

const price = new Money(100, 'USD')
+price
`${price}`
price + 50
js
class TypedArray {
  static [Symbol.hasInstance](instance) {
    return Array.isArray(instance) && instance.every(item => typeof item === 'number')
  }
}

[1, 2, 3] instanceof TypedArray
[1, '2', 3] instanceof TypedArray
js
class SafeArray extends Array {
  static get [Symbol.species]() {
    return Array
  }
}

const safe = new SafeArray(1, 2, 3)
const mapped = safe.map(x => x * 2)
mapped instanceof SafeArray
mapped instanceof Array

Symbol.iterator

Symbol.iterator 是可迭代协议的核心,它将在下一节详细展开。


Iterator 与 for...of

可迭代协议

JavaScript 定义了两个协议来实现迭代:

  • 可迭代协议(Iterable Protocol):对象实现 Symbol.iterator 方法,该方法返回一个迭代器。
  • 迭代器协议(Iterator Protocol):对象实现 next() 方法,返回 { value, done } 形状的结果。

for...of 循环、展开运算符、数组解构、Promise.all 等语法特性都依赖可迭代协议。

js
const arr = [10, 20, 30]
const iterator = arr[Symbol.iterator]()

iterator.next()
iterator.next()
iterator.next()
iterator.next()

自定义迭代器

js
class Range {
  constructor(start, end, step = 1) {
    this.start = start
    this.end = end
    this.step = step
  }

  [Symbol.iterator]() {
    let current = this.start
    const { end, step } = this

    return {
      next() {
        if (current <= end) {
          const value = current
          current += step
          return { value, done: false }
        }
        return { value: undefined, done: true }
      },

      [Symbol.iterator]() {
        return this
      }
    }
  }
}

const range = new Range(1, 5)
for (const n of range) {
  console.log(n)
}

[...new Range(0, 10, 2)]

const [a, b, c] = new Range(100, 200, 50)

无限迭代器

迭代器可以是无限的——只需永远不返回 done: true。这时必须由消费方(如 for...of 中的 break)来终止迭代:

js
function naturals(start = 0) {
  return {
    [Symbol.iterator]() {
      let n = start
      return {
        next() {
          return { value: n++, done: false }
        }
      }
    }
  }
}

for (const n of naturals()) {
  if (n > 5) break
  console.log(n)
}

for...of 与 for...in 的本质区别

for...in 遍历对象的可枚举字符串属性键(包含原型链),for...of 消费迭代器产出的值。二者在语义层面完全不同:

js
const arr = [10, 20, 30]

for (const key in arr) {
  console.log(key, typeof key)
}

for (const val of arr) {
  console.log(val, typeof val)
}

Generator

Generator 函数(function*)是 ES6 引入的一种特殊函数,它能够暂停和恢复执行。调用 Generator 函数不会立即执行函数体,而是返回一个 Generator 对象,该对象同时实现了迭代器协议和可迭代协议。

基本用法与 yield

js
function* countdown(n) {
  while (n > 0) {
    yield n
    n--
  }
}

const gen = countdown(3)
gen.next()
gen.next()
gen.next()
gen.next()

Generator 函数执行到 yield 时暂停,下次调用 next() 时从暂停处恢复执行。内部通过保存执行上下文(包括局部变量、PC 指针等)实现挂起/恢复,本质上是协程(Coroutine) 的一种实现。

yield* 委托

yield* 将迭代委托给另一个可迭代对象:

js
function* concat(...iterables) {
  for (const iterable of iterables) {
    yield* iterable
  }
}

[...concat([1, 2], [3, 4], 'ab')]

yield* 的返回值是被委托 Generator 的 return 值:

js
function* inner() {
  yield 'a'
  yield 'b'
  return 'inner done'
}

function* outer() {
  const result = yield* inner()
  yield result
}

[...outer()]

双向通信

Generator 最强大的特性之一是双向数据通道——next(value) 传入的参数会成为上一个 yield 表达式的返回值:

js
function* conversation() {
  const name = yield 'What is your name?'
  const age = yield `Hello ${name}! How old are you?`
  return `${name} is ${age} years old.`
}

const talk = conversation()
talk.next()
talk.next('Alice')
talk.next(30)

这种双向通信能力使 Generator 可以实现复杂的控制流。throw()return() 方法补充了错误注入和提前终止的能力:

js
function* resilient() {
  try {
    const a = yield 1
    const b = yield 2
    return a + b
  } catch (e) {
    yield `Error caught: ${e.message}`
  } finally {
    console.log('cleanup')
  }
}

const gen2 = resilient()
gen2.next()
gen2.throw(new Error('oops'))
gen2.next()

异步 Generator

ES2018 引入了异步 Generator(async function*),融合了异步迭代与 Generator 的暂停能力:

js
async function* fetchPages(baseUrl, totalPages) {
  for (let page = 1; page <= totalPages; page++) {
    const response = await fetch(`${baseUrl}?page=${page}`)
    const data = await response.json()
    yield data
  }
}

async function processAllPages() {
  for await (const pageData of fetchPages('/api/items', 5)) {
    console.log(`Got ${pageData.items.length} items`)
  }
}

异步 Generator 返回的对象实现了异步可迭代协议——Symbol.asyncIterator 方法返回异步迭代器,其 next() 返回 Promise<{ value, done }>

js
class EventStream {
  constructor(element, eventName) {
    this.element = element
    this.eventName = eventName
  }

  [Symbol.asyncIterator]() {
    const queue = []
    let resolve = null

    this.element.addEventListener(this.eventName, (event) => {
      if (resolve) {
        resolve({ value: event, done: false })
        resolve = null
      } else {
        queue.push(event)
      }
    })

    return {
      next() {
        if (queue.length > 0) {
          return Promise.resolve({ value: queue.shift(), done: false })
        }
        return new Promise(r => { resolve = r })
      }
    }
  }
}

Map / Set / WeakMap / WeakSet

ES6 引入的四种集合类型弥补了 JavaScript 长期以来只能用普通对象模拟映射和集合的缺陷。

Map

Map 是真正的键值对集合,与对象的本质区别在于:键可以是任意类型(包括对象、函数、NaN),且保持插入顺序

js
const map = new Map()

const objKey = { id: 1 }
const fnKey = () => {}

map.set(objKey, 'object value')
map.set(fnKey, 'function value')
map.set(NaN, 'NaN value')

map.get(objKey)
map.get(NaN)
map.has(fnKey)
map.size

Map 的键比较使用 SameValueZero 算法,它与 === 的唯一区别是 NaN === NaNtrue

js
const map2 = new Map([
  ['key1', 'value1'],
  ['key2', 'value2']
])

for (const [key, value] of map2) {
  console.log(key, value)
}

const arr = [...map2.entries()]
const keys = [...map2.keys()]
const values = [...map2.values()]

Set

Set 是值的集合,自动去重。同样使用 SameValueZero 算法判断唯一性。

js
const set = new Set([1, 2, 3, 2, 1])
set.size

set.add(4)
set.has(3)
set.delete(2)

const unique = [...new Set([1, 1, 2, 2, 3, 3])]

const a = new Set([1, 2, 3, 4])
const b = new Set([3, 4, 5, 6])

const union = new Set([...a, ...b])
const intersection = new Set([...a].filter(x => b.has(x)))
const difference = new Set([...a].filter(x => !b.has(x)))

WeakMap

WeakMap 的键必须是对象或 Symbol(非注册 Symbol),且键是弱引用——当键对象没有其他引用时,垃圾回收器可以回收键和对应的值。

js
const wm = new WeakMap()
let element = document.createElement('div')

wm.set(element, { clicks: 0 })
wm.get(element)

element = null

WeakMap 不可迭代、没有 size 属性,这是因为其内容取决于垃圾回收的时机,是不确定的。典型应用场景:

js
const privateData = new WeakMap()

class Person {
  constructor(name, age) {
    privateData.set(this, { name, age })
  }

  getName() {
    return privateData.get(this).name
  }

  getAge() {
    return privateData.get(this).age
  }
}

const p = new Person('Alice', 30)
p.getName()
privateData.get(p)
js
const cache = new WeakMap()

function computeExpensive(obj) {
  if (cache.has(obj)) {
    return cache.get(obj)
  }
  const result = JSON.stringify(obj).length * Math.random()
  cache.set(obj, result)
  return result
}

WeakSet

WeakSet 与 WeakMap 类似,存储对象的弱引用集合,常用于"标记"对象:

js
const visited = new WeakSet()

function processNode(node) {
  if (visited.has(node)) return
  visited.add(node)
  console.log(`Processing: ${node.id}`)
}

可选链 ?. 与空值合并 ??

可选链操作符

可选链 ?.(ES2020)在访问深层嵌套属性时,如果链上某个引用为 nullundefined,表达式会短路并返回 undefined,而非抛出 TypeError。

js
const user = {
  profile: {
    address: {
      city: 'Beijing'
    }
  }
}

const city = user?.profile?.address?.city
const zip = user?.profile?.address?.zip
const country = user?.settings?.region?.country

const arr = [1, [2, [3]]]
arr?.[1]?.[1]?.[0]

const obj = {
  greet() { return 'hello' }
}
obj.greet?.()
obj.missing?.()

可选链的规范行为是:当 ?. 左侧的值为 nullundefined 时,整个链的剩余部分不再求值(短路求值),直接返回 undefined。这意味着 ?. 后面的属性访问、方法调用、计算属性等都不会执行。

空值合并操作符

??(ES2020)在左侧为 nullundefined 时返回右侧值。与 || 的关键区别在于:|| 对所有 falsy 值(0''falseNaN)都会取右侧值,而 ?? 仅对 nullundefined 生效。

js
const count = 0
count || 10
count ?? 10

const text = ''
text || 'default'
text ?? 'default'

const isActive = false
isActive || true
isActive ?? true

组合使用

js
const config = {
  server: {
    port: 0,
    host: ''
  }
}

const port = config?.server?.port ?? 8080
const host = config?.server?.host ?? 'localhost'
const timeout = config?.server?.timeout ?? 3000

逻辑赋值运算符

ES2021 引入的逻辑赋值运算符与 ?? 形成完整的生态:

js
let a = null
a ??= 'default'

let b = 0
b ||= 42

let c = 1
c &&= 2

a ??= b 等价于 a ?? (a = b),注意它是短路的——如果 a 不是 nullish,赋值操作不会发生(不会触发 setter)。


BigInt

BigInt(ES2020)是 JavaScript 的第八种原始类型,用于表示任意精度的整数。它的引入解决了 Number 类型在超过 Number.MAX_SAFE_INTEGER(2^53 - 1)后精度丢失的问题。

js
const big = 9007199254740993n
const also = BigInt('9007199254740993')

9007199254740992n === 9007199254740993n

Number(9007199254740993)

运算规则

BigInt 支持所有算术运算符,但不能与 Number 混合运算

js
10n + 20n
2n ** 100n
100n / 3n

typeof 42n

BigInt 在除法中会截断小数部分(类似整数除法),这与 Number 的浮点除法行为不同。

比较与条件

js
42n === 42
42n == 42

0n ? 'truthy' : 'falsy'
1n ? 'truthy' : 'falsy'

const sorted = [3n, 1, 4n, 1n, 5, 9n, 2n].sort((a, b) => {
  if (a < b) return -1
  if (a > b) return 1
  return 0
})

BigInt 不能用于 Math 对象的方法,不能与 JSON.stringify 直接序列化(会抛错),需要自定义序列化逻辑。


globalThis

globalThis(ES2020)提供了一种跨环境获取全局对象的标准方式。在此之前,不同环境中全局对象的访问方式不一致:

环境全局对象
浏览器window / self / frames
Web Workerself
Node.jsglobal
js
globalThis.setTimeout === setTimeout

globalThis.myGlobal = 'accessible everywhere'

const getGlobal = () => {
  if (typeof globalThis !== 'undefined') return globalThis
  if (typeof window !== 'undefined') return window
  if (typeof global !== 'undefined') return global
  if (typeof self !== 'undefined') return self
  throw new Error('Unable to locate global object')
}

globalThis 的规范定义确保它在所有 JavaScript 环境中都是可写、可配置的全局属性,指向当前环境的全局对象。它的引入让编写跨平台(浏览器 / Node.js / Deno / Worker)的代码变得更加一致。

js
function isNodeEnvironment() {
  return typeof globalThis.process !== 'undefined'
    && typeof globalThis.process.versions?.node !== 'undefined'
}

function isBrowserEnvironment() {
  return typeof globalThis.window !== 'undefined'
    && typeof globalThis.document !== 'undefined'
}

知识脉络总结

ES6+ 的特性并非孤立存在,它们形成了一张紧密关联的知识网络:

  • 迭代器协议是数组解构、展开运算符、for...ofPromise.all 等众多语法特性的底层基础。
  • Generator 是迭代器协议的最佳实践方式,也是 async/await 的前身(async 函数本质上是自动执行的 Generator)。
  • Proxy/Reflect 为元编程打开了大门,是现代响应式框架(Vue 3)和各种工具库的基石。
  • Symbol 作为语言级协议标识符,连接了迭代器(Symbol.iterator)、类型转换(Symbol.toPrimitive)等核心机制。
  • WeakMap/WeakSet 与垃圾回收机制深度耦合,是实现"不泄漏的关联数据"的唯一正确方案。
  • 可选链与空值合并看似简单,却深刻影响了代码的防御性编程范式。

理解这些特性之间的关联,比单独记忆每个 API 更加重要。

用心学习,用代码说话 💻