Skip to content

ECMAScript 2020(ES11)

Optional Chaining (?.)

安全地访问嵌套对象属性,避免因中间值为 null 或 undefined 导致的错误。

javascript
{
  const user = {
    name: 'Alice',
    address: {
      street: 'Main St',
      getNum() {
        return '123'
      },
      dataList: [
        { details: 'detail1' },
        { details: 'detail2' }
      ]
    }
  }

  // 传统方式 - 冗长且易出错
  const street = user && user.address && user.address.street
  const num = user && user.address && user.address.getNum && user.address.getNum()
  const details = user && user.address && user.address.dataList && 
                  user.address.dataList[0] && user.address.dataList[0].details

  // 使用可选链 - 简洁安全
  const street2 = user?.address?.street
  const num2 = user?.address?.getNum?.()
  const details2 = user?.address?.dataList?.[0]?.details

  console.log(street2) // 'Main St'
  console.log(num2) // '123'
  console.log(details2) // 'detail1'

  // 处理不存在的属性
  const nonExistent = user?.profile?.avatar?.url
  console.log(nonExistent) // undefined (而不是报错)

  // 动态属性访问
  const property = 'street'
  const dynamicValue = user?.address?.[property]
  console.log(dynamicValue) // 'Main St'
}

Nullish Coalescing (??)

空值合并运算符,只有当左侧为 nullundefined 时才使用右侧的默认值。

javascript
{
  // 与 || 的区别
  console.log('' || 'default')  // 'default'
  console.log('' ?? 'default')  // ''

  console.log(0 || 'default')   // 'default'
  console.log(0 ?? 'default')   // 0

  console.log(false || 'default') // 'default'
  console.log(false ?? 'default') // false

  console.log(null ?? 'default')      // 'default'
  console.log(undefined ?? 'default') // 'default'

  // 实际应用场景
  function processConfig(config) {
    // 保留有意义的假值,只处理真正的"空值"
    const timeout = config.timeout ?? 5000
    const retries = config.retries ?? 3
    const debug = config.debug ?? false
    
    return { timeout, retries, debug }
  }

  console.log(processConfig({ timeout: 0, debug: false }))
  // { timeout: 0, retries: 3, debug: false }
  
  console.log(processConfig({ timeout: null, debug: undefined }))
  // { timeout: 5000, retries: 3, debug: false }

  // 与可选链结合使用
  const userSettings = {
    theme: null,
    notifications: {
      email: false,
      push: undefined
    }
  }

  const theme = userSettings?.theme ?? 'light'
  const emailNotifs = userSettings?.notifications?.email ?? true
  const pushNotifs = userSettings?.notifications?.push ?? true

  console.log({ theme, emailNotifs, pushNotifs })
  // { theme: 'light', emailNotifs: false, pushNotifs: true }
}

BigInt

新的原始数据类型,用于表示任意精度的大整数。

javascript
{
  // JavaScript Number 类型的限制
  console.log(Number.MAX_SAFE_INTEGER) // 9007199254740991
  console.log(2 ** 53) // 9007199254740992
  console.log(2 ** 53 + 1) // 9007199254740992 (精度丢失!)

  // BigInt 可以表示任意大的整数
  const bigInt1 = 9007199254740993n
  const bigInt2 = BigInt(9007199254740993)
  const bigInt3 = BigInt('9007199254740994')

  console.log(bigInt1) // 9007199254740993n
  console.log(typeof bigInt1) // 'bigint'

  // BigInt 运算
  const result = 9007199254740991n + 2n
  console.log(result) // 9007199254740993n

  // 大数计算示例
  const factorial = (n) => {
    if (n <= 1n) return 1n
    return n * factorial(n - 1n)
  }
  console.log(factorial(25n)) // 15511210043330985984000000n

  // 比较操作
  console.log(1n == 1)   // true (类型转换)
  console.log(1n === 1)  // false (类型严格比较)
  console.log(1n < 2)    // true
  console.log(2n > 1)    // true

  // 注意事项
  // 1. 不能与 Number 混合运算
  // console.log(1n + 1) // TypeError

  // 2. 需要显式转换
  console.log(Number(1n) + 1) // 2
  console.log(1n + BigInt(1)) // 2n

  // 3. BigInt 不能使用 Math 对象的方法
  // console.log(Math.sqrt(4n)) // TypeError

  // 4. JSON.stringify 不直接支持 BigInt
  const obj = { bigNum: 123n }
  // console.log(JSON.stringify(obj)) // TypeError
  
  // 需要自定义序列化
  console.log(JSON.stringify(obj, (key, value) =>
    typeof value === 'bigint' ? value.toString() : value
  )) // '{"bigNum":"123"}'
}

globalThis

提供跨平台访问全局对象的标准方式。

javascript
{
  // 传统方式 - 平台兼容性差
  function getGlobal() {
    if (typeof self !== 'undefined') return self         // Web Workers
    if (typeof window !== 'undefined') return window     // 浏览器
    if (typeof global !== 'undefined') return global     // Node.js
    throw new Error('Unable to locate global object')
  }

  // 现代方式 - 统一的全局对象访问
  console.log(typeof globalThis) // 'object'

  // 在不同环境中都能正常工作
  globalThis.myGlobalVar = 'Hello World'
  console.log(globalThis.myGlobalVar) // 'Hello World'

  // 实际应用:polyfill 检测
  if (!globalThis.fetch) {
    // 在 Node.js 环境中添加 fetch polyfill
    globalThis.fetch = require('node-fetch')
  }

  // 检查全局 API 是否存在
  if (typeof globalThis.setTimeout === 'function') {
    console.log('Timer APIs available')
  }

  // 跨平台的全局变量设置
  globalThis.APP_CONFIG = {
    version: '1.0.0',
    env: 'production'
  }
}

Dynamic import()

动态导入模块,返回 Promise,支持按需加载。

javascript
{
  // 基本用法
  async function loadModule() {
    try {
      const module = await import('./utils.js')
      module.doSomething()
    } catch (error) {
      console.error('Failed to load module:', error)
    }
  }

  // 条件导入
  async function loadFeature(featureName) {
    const modulePath = `./features/${featureName}.js`
    const module = await import(modulePath)
    return module.default
  }

  // 与传统 import 语句的区别
  // 静态导入 (编译时)
  // import { helper } from './helper.js'

  // 动态导入 (运行时)
  const loadHelper = async () => {
    const { helper } = await import('./helper.js')
    return helper
  }

  // 实际应用:路由懒加载
  const routes = {
    '/home': () => import('./pages/Home.js'),
    '/about': () => import('./pages/About.js'),
    '/contact': () => import('./pages/Contact.js')
  }

  async function navigateTo(path) {
    const loadPage = routes[path]
    if (loadPage) {
      const pageModule = await loadPage()
      pageModule.default.render()
    }
  }

  // 代码分割示例
  button.addEventListener('click', async () => {
    const { heavyLibrary } = await import('./heavy-library.js')
    heavyLibrary.process()
  })

  // 动态导入 JSON
  async function loadConfig() {
    const config = await import('./config.json', {
      assert: { type: 'json' }
    })
    return config.default
  }
}

String.prototype.matchAll()

返回所有匹配正则表达式的迭代器,包含捕获组信息。

javascript
{
  const str = `
    <html>
      <body>
        <div>第一个div</div>
        <p>这是一个p</p>
        <span>span</span>
        <div>第二个div</div>
      </body>
    </html>
  `
  const regExp = /<div>(.*?)<\/div>/g

  // 传统方法1:使用 exec() 循环
  function selectDivExec(regExp, str) {
    const matches = []
    let match
    while ((match = regExp.exec(str)) !== null) {
      matches.push(match[1])
    }
    return matches
  }

  // 传统方法2:使用 replace()
  function selectDivReplace(regExp, str) {
    const matches = []
    str.replace(regExp, (all, first) => {
      matches.push(first)
    })
    return matches
  }

  // 现代方法:使用 matchAll()
  function selectDivMatchAll(regExp, str) {
    const matches = []
    for (const match of str.matchAll(regExp)) {
      matches.push(match[1])
    }
    return matches
  }

  console.log(selectDivExec(regExp, str))      // ['第一个div', '第二个div']
  console.log(selectDivReplace(regExp, str))   // ['第一个div', '第二个div']
  console.log(selectDivMatchAll(regExp, str))  // ['第一个div', '第二个div']

  // 更复杂的示例:解析 URL 参数
  const url = 'https://example.com?name=Alice&age=25&city=NewYork&name=Bob'
  const paramRegex = /([^&=]+)=([^&]*)/g

  // 使用 matchAll 获取所有参数
  const params = new Map()
  for (const match of url.matchAll(paramRegex)) {
    const [, key, value] = match
    if (params.has(key)) {
      // 处理重复参数
      const existing = params.get(key)
      params.set(key, Array.isArray(existing) ? [...existing, value] : [existing, value])
    } else {
      params.set(key, value)
    }
  }

  console.log(params)
  // Map(3) {
  //   'name' => ['Alice', 'Bob'],
  //   'age' => '25',
  //   'city' => 'NewYork'
  // }

  // 获取匹配的详细信息
  const text = 'The dates are 2023-12-25 and 2024-01-01'
  const dateRegex = /(\d{4})-(\d{2})-(\d{2})/g

  for (const match of text.matchAll(dateRegex)) {
    console.log(`Full match: ${match[0]}`)
    console.log(`Year: ${match[1]}, Month: ${match[2]}, Day: ${match[3]}`)
    console.log(`Index: ${match.index}`)
    console.log('---')
  }
}

Promise.allSettled()

等待所有 Promise 完成(无论成功或失败),返回所有结果的状态信息。

javascript
{
  // 与 Promise.all() 的对比
  const promises = [
    Promise.reject({ code: 500, msg: '服务异常' }),
    Promise.resolve({ code: 200, data: ['1', '2', '3'] }),
    Promise.resolve({ code: 200, data: ['4', '5', '6'] })
  ]

  // Promise.all() - 任一失败就整体失败
  Promise.all(promises)
    .then(res => console.log('All succeeded:', res))
    .catch(err => console.log('One failed:', err))
  // 输出: One failed: { code: 500, msg: '服务异常' }

  // Promise.allSettled() - 等待所有完成
  Promise.allSettled(promises)
    .then(results => {
      console.log('All settled:')
      results.forEach((result, index) => {
        if (result.status === 'fulfilled') {
          console.log(`Promise ${index} succeeded:`, result.value)
        } else {
          console.log(`Promise ${index} failed:`, result.reason)
        }
      })

      // 过滤成功的结果
      const successful = results
        .filter(result => result.status === 'fulfilled')
        .map(result => result.value)
      
      console.log('Successful results:', successful)
    })

  // 实际应用:批量 API 调用
  async function fetchMultipleAPIs() {
    const apiCalls = [
      fetch('/api/users'),
      fetch('/api/posts'),
      fetch('/api/comments')
    ]

    const results = await Promise.allSettled(apiCalls)
    
    const responses = {}
    results.forEach((result, index) => {
      const apiName = ['users', 'posts', 'comments'][index]
      
      if (result.status === 'fulfilled') {
        responses[apiName] = result.value
      } else {
        console.error(`Failed to fetch ${apiName}:`, result.reason)
        responses[apiName] = null
      }
    })

    return responses
  }

  // 批量文件上传示例
  async function uploadFiles(files) {
    const uploadPromises = files.map(file => 
      fetch('/upload', {
        method: 'POST',
        body: file
      })
    )

    const results = await Promise.allSettled(uploadPromises)
    
    const report = {
      successful: 0,
      failed: 0,
      errors: []
    }

    results.forEach((result, index) => {
      if (result.status === 'fulfilled') {
        report.successful++
      } else {
        report.failed++
        report.errors.push({
          file: files[index].name,
          error: result.reason.message
        })
      }
    })

    return report
  }
}