Skip to content

ECMAScript 2021(ES12)

Logical Assignment Operators

&&= (逻辑与赋值)

只有当左侧操作数为真值时,才会进行赋值操作。

javascript
{
  let num1 = 5
  let num2 = 10

  num1 &&= num2
  console.log(num1) // 10

  let num3 = 0
  num3 &&= 99
  console.log(num3) // 0 (因为 0 是假值,不会赋值)

  // 等价于
  // num1 && (num1 = num2)
  // if (num1) {
  //   num1 = num2
  // }
}

||= (逻辑或赋值)

只有当左侧操作数为假值时,才会进行赋值操作。

javascript
{
  let num1
  let num2 = 10

  num1 ||= num2
  console.log(num1) // 10

  let num3 = 'existing'
  num3 ||= 'default'
  console.log(num3) // 'existing' (已有值,不会被替换)

  // 等价于
  // num1 || (num1 = num2)
  // if (!num1) {
  //   num1 = num2
  // }

  // 实际应用:设置默认值
  let config = {}
  config.timeout ||= 5000
  config.retries ||= 3
  console.log(config) // { timeout: 5000, retries: 3 }
}

??= (空值合并赋值)

只有当左侧操作数为 nullundefined 时,才会进行赋值操作。

javascript
{
  let num1
  let num2 = 10

  num1 ??= num2
  console.log(num1) // 10

  // 与 ||= 的区别
  let num3 = false
  num3 ??= num2
  console.log(num3) // false (false 不是 null 或 undefined,不会赋值)

  let num4 = null
  num4 ??= 123
  console.log(num4) // 123

  // 等价于
  // num1 ?? (num1 = num2)

  // 实际应用:保留 0、false、'' 等假值
  let settings = {
    volume: 0,
    enabled: false,
    theme: null
  }
  
  settings.volume ??= 50    // 保持 0
  settings.enabled ??= true // 保持 false
  settings.theme ??= 'dark' // 设置为 'dark'
  
  console.log(settings) // { volume: 0, enabled: false, theme: 'dark' }
}

Numeric Separators

使用下划线 _ 作为数字分隔符,提高大数字的可读性。

javascript
{
  // 整数
  const million = 1_000_000
  const billion = 1_000_000_000
  
  console.log(million) // 1000000
  console.log(billion) // 1000000000

  // 小数
  const decimal = 1000.12_34_56
  console.log(decimal) // 1000.123456

  // 二进制、八进制、十六进制
  const binary = 0b1010_0001_1000_0101
  const octal = 0o777_444
  const hex = 0xFF_EC_DE_5E

  console.log(binary) // 41349
  console.log(octal) // 261924
  console.log(hex) // 4293837150

  // 科学计数法
  const scientific = 1.234_567e8
  console.log(scientific) // 123456700

  // 注意:分隔符不影响实际值,只是视觉上的分组
  console.log(1_000 === 1000) // true
}

Promise.any()

返回第一个成功(fulfilled)的 Promise,所有 Promise 都失败时才会拒绝。

javascript
{
  // 基本用法:返回最快成功的 Promise
  Promise.any([
    new Promise(resolve => setTimeout(() => resolve('Fast'), 100)),
    new Promise(resolve => setTimeout(() => resolve('Slow'), 500)),
    Promise.reject('Error')
  ])
  .then(result => console.log(result)) // 'Fast'

  // 与 Promise.race() 的区别
  Promise.race([
    Promise.reject('Error 1'),
    new Promise(resolve => setTimeout(() => resolve('Success'), 100))
  ])
  .catch(err => console.log('Race failed:', err)) // 'Race failed: Error 1'

  Promise.any([
    Promise.reject('Error 1'),
    new Promise(resolve => setTimeout(() => resolve('Success'), 100))
  ])
  .then(result => console.log('Any succeeded:', result)) // 'Any succeeded: Success'

  // 所有 Promise 都失败时返回 AggregateError
  Promise.any([
    Promise.reject('Error 1'),
    Promise.reject('Error 2'),
    Promise.reject('Error 3')
  ])
  .catch(err => {
    console.log(err.name) // 'AggregateError'
    console.log(err.errors) // ['Error 1', 'Error 2', 'Error 3']
  })

  // 实际应用:多个资源竞争加载
  async function loadResource() {
    try {
      const data = await Promise.any([
        fetch('/api/data-server1'),
        fetch('/api/data-server2'),
        fetch('/api/data-server3')
      ])
      return await data.json()
    } catch (error) {
      console.log('All servers failed:', error.errors)
      throw new Error('All servers are unavailable')
    }
  }
}

String.prototype.replaceAll()

替换字符串中所有匹配的模式,无需使用全局正则表达式。

javascript
{
  const str = 'I belong to ES12 new API, ES12 is great, ES12!'

  // 传统方法:只替换第一个匹配
  console.log(str.replace('ES12', 'ES2021')) 
  // 'I belong to ES2021 new API, ES12 is great, ES12!'

  // 传统方法:使用全局正则替换所有
  console.log(str.replace(/ES12/g, 'ES2021'))
  // 'I belong to ES2021 new API, ES2021 is great, ES2021!'

  // 新方法:replaceAll 直接替换所有
  console.log(str.replaceAll('ES12', 'ES2021'))
  // 'I belong to ES2021 new API, ES2021 is great, ES2021!'

  // 使用函数作为替换值
  const result = 'hello world hello'.replaceAll('hello', (match, offset) => {
    return `${match}(${offset})`
  })
  console.log(result) // 'hello(0) world hello(12)'

  // 使用正则表达式(必须带全局标志)
  console.log(str.replaceAll(/ES12/g, 'ES2021'))
  // 'I belong to ES2021 new API, ES2021 is great, ES2021!'

  // 错误用法:正则表达式不带全局标志会报错
  // console.log(str.replaceAll(/ES12/, 'ES2021'))
  // TypeError: String.prototype.replaceAll called with a non-global RegExp argument

  // 实际应用:安全地替换特殊字符
  const template = 'Hello {{name}}, welcome to {{place}}!'
  const filled = template
    .replaceAll('{{name}}', 'Alice')
    .replaceAll('{{place}}', 'Wonderland')
  console.log(filled) // 'Hello Alice, welcome to Wonderland!'
}

WeakRef

创建对对象的弱引用,不会阻止垃圾回收。

注意: 正确使用 WeakRef 需要仔细考虑,建议尽量避免使用。垃圾回收的时机和行为在不同 JavaScript 引擎中可能不同。

javascript
{
  // 基本用法
  const obj = { name: 'example', data: [1, 2, 3] }
  const weakRef = new WeakRef(obj)

  // 获取引用的对象
  const target = weakRef.deref()
  if (target) {
    console.log(target.name) // 'example'
  } else {
    console.log('Object has been garbage collected')
  }

  // 实际应用:缓存管理
  class Cache {
    constructor() {
      this.cache = new Map()
    }

    set(key, value) {
      this.cache.set(key, new WeakRef(value))
    }

    get(key) {
      const ref = this.cache.get(key)
      if (!ref) return undefined

      const value = ref.deref()
      if (value === undefined) {
        // 对象已被回收,清理缓存
        this.cache.delete(key)
        return undefined
      }
      return value
    }

    cleanup() {
      for (const [key, ref] of this.cache.entries()) {
        if (ref.deref() === undefined) {
          this.cache.delete(key)
        }
      }
    }
  }

  const cache = new Cache()
  let largeObject = { data: new Array(1000000).fill('data') }
  
  cache.set('large', largeObject)
  console.log(cache.get('large')) // 对象存在
  
  largeObject = null // 移除强引用
  // 在垃圾回收后,cache.get('large') 可能返回 undefined
}

FinalizationRegistry

当对象被垃圾回收时执行清理回调。

javascript
{
  // 创建清理注册表
  const registry = new FinalizationRegistry((heldValue) => {
    console.log(`Cleaning up: ${heldValue}`)
  })

  // 注册对象
  let obj = { name: 'test' }
  registry.register(obj, 'test-object')

  // 当 obj 被垃圾回收时,会执行回调

  // 实际应用:DOM 元素计数器示例
  class Counter {
    constructor(element) {
      this.ref = new WeakRef(element)
      this.count = 0
      
      // 注册清理
      const registry = new FinalizationRegistry(() => {
        console.log('Counter element was garbage collected')
        this.stop()
      })
      registry.register(element, undefined, this)
      
      this.start()
    }

    start() {
      if (this.timer) return

      const tick = () => {
        const element = this.ref.deref()
        if (element) {
          element.textContent = ++this.count
        } else {
          console.log('Element no longer exists')
          this.stop()
        }
      }

      tick()
      this.timer = setInterval(tick, 1000)
    }

    stop() {
      if (this.timer) {
        clearInterval(this.timer)
        this.timer = null
      }
    }
  }

  // 使用示例(在 DOM 环境中)
  /*
  const counterElement = document.createElement('div')
  document.body.appendChild(counterElement)
  
  const counter = new Counter(counterElement)
  
  // 5秒后移除元素
  setTimeout(() => {
    counterElement.remove()
  }, 5000)
  */
}