Skip to content
⚠️ This article was written in 2019. Some content may be outdated.

ES2020 New Features Summary

The TC-39 committee adds new features to JavaScript every year. ES2020 (ES11) has finished the proposal process and is expected to ship officially in June 2020. This update brings several very practical features—especially Optional Chaining and Nullish Coalescing, which can significantly cut down the defensive checks in our everyday code. Let's go through them one by one.

Optional Chaining (?.) ​

Optional Chaining is probably the most-used feature in ES2020. When you access deeply nested object properties, if any intermediate property is null or undefined, it returns undefined directly instead of throwing.

javascript
// 以前的写法:层层判断
function getCity(user) {
  if (user && user.address && user.address.city) {
    return user.address.city
  }
  return undefined
}

// 使用 Optional Chaining
function getCity(user) {
  return user?.address?.city
}

// 适用场景一:访问深层属性
const user = {
  name: '张三',
  address: {
    city: '北京'
  }
}

console.log(user?.address?.city)      // '北京'
console.log(user?.company?.name)      // undefined(不报错)
console.log(user?.['address']?.city)  // '北京'(支持括号访问)

// 适用场景二:调用可能不存在的方法
const api = {
  getData() { return { items: [1, 2, 3] } }
}

const data = api?.getData?.()   // { items: [1, 2, 3] }
const missing = api?.missing?.() // undefined(不报错)

// 适用场景三:访问数组元素
const arr = [1, 2, 3]
console.log(arr?.[0])  // 1

const empty = null
console.log(empty?.[0]) // undefined

// 实际项目中的应用:API 响应处理
async function fetchUser(id) {
  const response = await fetch(`/api/users/${id}`)
  const data = await response.json()

  // 不再需要冗长的判断
  const userName = data?.result?.user?.name ?? '未知用户'
  const avatar = data?.result?.user?.profile?.avatar ?? '/default-avatar.png'

  return { userName, avatar }
}

Nullish Coalescing (??) ​

The Nullish Coalescing operator ?? is similar to ||, but it only returns the right-hand value when the left side is null or undefined. This distinction matters a lot when dealing with falsy values like 0, '', and false:

javascript
// || 的问题:会把所有 falsy 值都替换掉
const count = 0
console.log(count || 10)  // 10(0 被当作"假值"替换了)

const name = ''
console.log(name || '默认名称')  // '默认名称'(空字符串被替换了)

const enabled = false
console.log(enabled || true)  // true(false 被替换了)

// ?? 只在 null/undefined 时替换
console.log(count ?? 10)       // 0(0 被保留)
console.log(name ?? '默认名称') // ''(空字符串被保留)
console.log(enabled ?? true)   // false(false 被保留)

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

// 组合使用 Optional Chaining + Nullish Coalescing
const config = {
  server: {
    port: 0  // 显式设置端口为 0
  }
}

// 用 || 的话,port:0 会被错误地替换
const port1 = config?.server?.port || 3000  // 3000(错误!)

// 用 ?? 正确保留 0
const port2 = config?.server?.port ?? 3000  // 0(正确)

// 实际项目:表单默认值处理
function getFormValues(formData) {
  return {
    username: formData?.username ?? '',
    age: formData?.age ?? 0,
    enabled: formData?.enabled ?? false,
    tags: formData?.tags ?? []
  }
}

BigInt ​

BigInt is a new primitive type in JavaScript for representing integers of arbitrary precision. It removes the limit of Number.MAX_SAFE_INTEGER (2^53 - 1):

javascript
// Number 的精度限制
console.log(Number.MAX_SAFE_INTEGER)  // 9007199254740991
console.log(9007199254740991 + 1)     // 9007199254740992
console.log(9007199254740991 + 2)     // 9007199254740992(精度丢失!)

// BigInt:通过数字后加 n 创建
const bigNum = 9007199254740991n
console.log(bigNum + 1n)  // 9007199254740992n(正确)
console.log(bigNum + 2n)  // 9007199254740993n(正确)

// 或通过 BigInt() 函数创建
const another = BigInt('9007199254740991999999999999')

// 基本运算
console.log(100n + 200n)    // 300n
console.log(100n * 200n)    // 20000n
console.log(100n / 30n)     // 3n(整数除法,舍去小数)
console.log(100n % 30n)     // 10n

// 注意:BigInt 和 Number 不能混合运算
// console.log(100n + 200)  // TypeError
console.log(100n + BigInt(200))  // 300n(需要显式转换)

// 比较运算可以混合
console.log(100n > 50)    // true
console.log(100n === 100) // false(类型不同)
console.log(100n == 100)  // true(值相等)

// 实际应用场景:数据库 ID、金融计算
const userId = 1589328472619638784n  // Twitter Snowflake ID
const transactionId = 2019122300000000001n

Promise.allSettled ​

Promise.all rejects immediately as soon as any one Promise rejects. Promise.allSettled, by contrast, waits for all Promises to finish (whether they succeed or fail) and returns the result status of each one:

javascript
// Promise.all 的问题:一个失败就全部失败
const promises = [
  fetch('/api/users').then(r => r.json()),
  fetch('/api/orders').then(r => r.json()),
  fetch('/api/products').then(r => r.json())
]

try {
  const results = await Promise.all(promises)
  // 如果 orders 请求失败,这里拿不到 users 和 products 的数据
} catch (error) {
  console.log(error) // 只知道有一个失败了,不知道是哪个
}

// Promise.allSettled:每个结果都有状态
const results = await Promise.allSettled([
  fetch('/api/users').then(r => r.json()),
  fetch('/api/orders').then(r => r.json()),
  fetch('/api/products').then(r => r.json())
])

// results 结构:
// [
//   { status: 'fulfilled', value: [...] },
//   { status: 'rejected', reason: Error('network error') },
//   { status: 'fulfilled', value: [...] }
// ]

// 方便地分离成功和失败的结果
const succeeded = results
  .filter(r => r.status === 'fulfilled')
  .map(r => r.value)

const failed = results
  .filter(r => r.status === 'rejected')
  .map(r => r.reason)

console.log('成功的请求:', succeeded)
console.log('失败的请求:', failed)

// 实际应用:批量操作,部分失败不影响其他
async function batchDelete(ids) {
  const results = await Promise.allSettled(
    ids.map(id => fetch(`/api/items/${id}`, { method: 'DELETE' }))
  )

  const deleted = []
  const errors = []

  results.forEach((result, index) => {
    if (result.status === 'fulfilled') {
      deleted.push(ids[index])
    } else {
      errors.push({ id: ids[index], error: result.reason })
    }
  })

  return { deleted, errors }
}

globalThis ​

In different JavaScript environments the global object has different names. globalThis provides a unified way to access the global object:

javascript
// 以前:需要判断环境
function getGlobalObject() {
  if (typeof window !== 'undefined') return window        // 浏览器
  if (typeof global !== 'undefined') return global        // Node.js
  if (typeof self !== 'undefined') return self            // Web Worker
  throw new Error('无法确定全局对象')
}

// ES2020:直接用 globalThis
console.log(globalThis)  // 在浏览器中是 window,在 Node.js 中是 global

// 实际应用:跨环境的全局变量存储
globalThis.__APP_CONFIG__ = {
  version: '1.0.0',
  env: 'production'
}

// 任何地方都可以访问
console.log(globalThis.__APP_CONFIG__)

// polyfill(如果需要支持旧环境)
if (typeof globalThis === 'undefined') {
  (function() {
    if (typeof window !== 'undefined') {
      window.globalThis = window
    } else if (typeof global !== 'undefined') {
      global.globalThis = global
    } else if (typeof self !== 'undefined') {
      self.globalThis = self
    }
  })()
}

Dynamic Import ​

Dynamic import() lets you load modules on demand at runtime and is the foundation of code splitting:

javascript
// 静态导入:在文件顶部,编译时确定
import { debounce } from 'lodash'

// 动态导入:在代码中按条件加载,运行时决定
async function loadChart() {
  // 只有用户点击"查看图表"时才加载 chart.js
  const chartModule = await import('chart.js')
  const Chart = chartModule.default

  const ctx = document.getElementById('myChart').getContext('2d')
  new Chart(ctx, { type: 'bar', data: chartData })
}

// 配合 React.lazy 实现路由级代码分割
import React, { lazy, Suspense } from 'react'

const Dashboard = lazy(() => import('./pages/Dashboard'))
const Settings = lazy(() => import('./pages/Settings'))

// 按条件加载模块
async function getParser(format) {
  switch (format) {
    case 'csv':
      return await import('./parsers/csv')
    case 'json':
      return await import('./parsers/json')
    case 'xml':
      return await import('./parsers/xml')
    default:
      throw new Error(`不支持的格式: ${format}`)
  }
}

// 预加载:提前下载但不执行
function preloadChart() {
  // link prefetch 或者 webpack magic comments
  import(/* webpackPrefetch: true */ 'chart.js')
}

// 错误处理
async function safeImport(modulePath) {
  try {
    const module = await import(modulePath)
    return module
  } catch (error) {
    console.error(`加载模块失败: ${modulePath}`, error)
    return null
  }
}

Syntax Examples Summary ​

javascript
// ES2020 全部新特性速览

// 1. Optional Chaining
const value = obj?.prop?.nested

// 2. Nullish Coalescing
const result = maybeNull ?? 'default'

// 3. BigInt
const big = 9007199254740993n

// 4. Promise.allSettled
const outcomes = await Promise.allSettled([p1, p2, p3])

// 5. globalThis
const global = globalThis

// 6. Dynamic Import
const mod = await import('./module')

// 7. String.prototype.matchAll(也是 ES2020 的)
const regex = /t(e)(st(\d?))/g
const str = 'test1test2'
const matches = [...str.matchAll(regex)]
// [['test1', 'e', 'st1'], ['test2', 'e', 'st2']]

// 8. import.meta(获取模块元信息)
console.log(import.meta.url)  // 当前模块的 URL

Summary ​

  • Optional Chaining (?.) makes deep property access free of nested checks and is the most practical new feature
  • Nullish Coalescing (??) fixes the problem of || wrongly replacing falsy values
  • BigInt solves the precision loss of large integers and fits IDs, finance, and similar scenarios
  • Promise.allSettled makes handling batch async results more flexible—no more "one fails, all fail"
  • globalThis unifies access to the global object across environments
  • Dynamic import() is the basis for code splitting and on-demand loading; it works best with React.lazy
  • These features are already supported in TypeScript 3.7+, so you can adopt them early

MIT Licensed