ES2018 和 ES2019

ES2018 和 ES2019 为 JavaScript 带来了新的特性和语法改进。

ES2018 新特性

异步迭代

// 异步生成器函数
async function* asyncGenerator() {
  let i = 0
  while (i < 3) {
    yield new Promise(resolve => setTimeout(() => resolve(i++), 1000))
  }
}

// 使用异步迭代
async function consumeAsyncGenerator() {
  for await (let value of asyncGenerator()) {
    console.log(value) // 0, 1, 2 (每秒一个)
  }
}

consumeAsyncGenerator()

Promise.prototype.finally()

// finally 方法总是会执行
function doSomething() {
  return fetch('/api/data')
    .then(response => response.json())
    .catch(error => console.error('Fetch failed:', error))
    .finally(() => {
      console.log('Cleanup completed')
      hideLoadingSpinner()
    })
}

// 无论成功还是失败都会执行清理
doSomething().then(data => {
  console.log('Data received:', data)
})

正则表达式改进

// s 标志:dotAll 模式
const regex1 = /./s
console.log(regex1.test('\n')) // true (点号匹配换行符)

// 命名捕获组
const regex2 = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/
const match = '2023-12-25'.match(regex2)
console.log(match.groups) // { year: '2023', month: '12', day: '25' }

// 后行断言
const regex3 = /(?<=@)\w+/  // 匹配 @ 后面的单词
console.log('user@email.com'.match(regex3)) // ['email']

// 前行断言
const regex4 = /\w+(?=\.com)/  // 匹配 .com 前面的单词
console.log('user@email.com'.match(regex4)) // ['email']

对象展开运算符

// 浅拷贝对象
const original = { a: 1, b: 2 }
const copy = { ...original }

// 合并对象
const obj1 = { a: 1, b: 2 }
const obj2 = { c: 3, d: 4 }
const merged = { ...obj1, ...obj2 } // { a: 1, b: 2, c: 3, d: 4 }

// 添加属性
const enhanced = { ...original, c: 3, d: 4 }

// 函数参数
function createUser(name, email, ...options) {
  return {
    name,
    email,
    ...options
  }
}

const user = createUser('John', 'john@example.com', { age: 30, active: true })

模板字符串改进

// 转义序列
const str1 = `Line 1\nLine 2`  // 有效的换行
const str2 = `Column 1\tColumn 2`  // 有效的制表符

// 标签函数中的转义
function myTag(strings, ...values) {
  return strings.reduce((result, str, i) => {
    return result + str + (values[i] || '')
  }, '')
}

const result = myTag`Hello \u{1F600} World`  // 支持 Unicode 转义

ES2019 新特性

Array.prototype.flat() 和 flatMap()

// flat() 扁平化数组
const nested = [1, [2, [3, [4]]]]
console.log(nested.flat())     // [1, 2, [3, [4]]]
console.log(nested.flat(2))    // [1, 2, 3, [4]]
console.log(nested.flat(Infinity)) // [1, 2, 3, 4]

// flatMap() 先映射再扁平化
const sentences = ['Hello world', 'How are you']
const words = sentences.flatMap(sentence => sentence.split(' '))
console.log(words) // ['Hello', 'world', 'How', 'are', 'you']

// 数值计算示例
const numbers = [1, 2, 3, 4]
const doubled = numbers.flatMap(x => [x, x * 2])
console.log(doubled) // [1, 2, 2, 4, 3, 6, 4, 8]

Object.fromEntries()

// 从键值对数组创建对象
const entries = [['name', 'John'], ['age', 30], ['city', 'New York']]
const obj = Object.fromEntries(entries)
console.log(obj) // { name: 'John', age: 30, city: 'New York' }

// Map 转换为对象
const map = new Map([['a', 1], ['b', 2], ['c', 3]])
const objFromMap = Object.fromEntries(map)
console.log(objFromMap) // { a: 1, b: 2, c: 3 }

// URL 参数转换为对象
const urlParams = new URLSearchParams('name=John&age=30')
const paramsObj = Object.fromEntries(urlParams)
console.log(paramsObj) // { name: 'John', age: '30' }

// 过滤和转换
const filteredObj = Object.fromEntries(
  Object.entries(obj).filter(([key, value]) => typeof value === 'string')
)

String.prototype.trimStart() 和 trimEnd()

// 移除开头空白
const str1 = '   Hello World   '
console.log(str1.trimStart())  // 'Hello World   '

// 移除结尾空白
console.log(str1.trimEnd())    // '   Hello World'

// 链式调用
console.log(str1.trimStart().trimEnd()) // 'Hello World'

// 别名方法
console.log(str1.trimLeft())   // 'Hello World   ' (trimStart 的别名)
console.log(str1.trimRight())  // '   Hello World' (trimEnd 的别名)

Symbol.prototype.description

// 获取 Symbol 的描述
const sym1 = Symbol('my symbol')
console.log(sym1.description) // 'my symbol'

const sym2 = Symbol()
console.log(sym2.description) // undefined

const sym3 = Symbol('')
console.log(sym3.description) // ''

// 自定义描述
function createSymbol(description) {
  return Symbol(description)
}

const mySymbol = createSymbol('unique identifier')
console.log(mySymbol.description) // 'unique identifier'

可选的 catch 绑定

// 传统的 try-catch
try {
  riskyOperation()
} catch (error) {
  console.error('An error occurred:', error)
}

// ES2019 可选 catch 绑定
try {
  riskyOperation()
} catch {
  console.error('An error occurred')
}

// 当不需要访问错误对象时很有用
const results = [
  tryProcess(item1),
  tryProcess(item2),
  tryProcess(item3)
].filter(Boolean)

function tryProcess(item) {
  try {
    return processItem(item)
  } catch {
    return null // 静默失败
  }
}

JSON 改进

// JSON.stringify() 改进
const obj = { a: 1, b: 2 }

// 格式化输出
console.log(JSON.stringify(obj, null, 2))
// {
//   "a": 1,
//   "b": 2
// }

// toJSON 方法支持
class User {
  constructor(name, age) {
    this.name = name
    this.age = age
    this.createdAt = new Date()
  }

  toJSON() {
    return {
      name: this.name,
      age: this.age,
      createdAt: this.createdAt.toISOString()
    }
  }
}

const user = new User('Alice', 25)
console.log(JSON.stringify(user))
// {"name":"Alice","age":25,"createdAt":"2023-12-07T10:30:00.000Z"}

实用示例

异步数据处理

// 使用异步迭代处理流数据
async function processLargeFile(file) {
  const stream = file.stream()
  let totalBytes = 0

  for await (const chunk of stream) {
    totalBytes += chunk.length
    console.log(`Processed ${totalBytes} bytes`)
  }

  return totalBytes
}

// Promise.finally 清理资源
function fetchWithCleanup(url) {
  const controller = new AbortController()

  return fetch(url, { signal: controller.signal })
    .then(response => response.json())
    .finally(() => {
      console.log('Request cleanup')
      // 清理资源
    })
}

现代对象操作

// 使用展开运算符和 Object.fromEntries
function updateUser(userId, updates) {
  const users = getUsersFromStorage()

  const updatedUsers = users.map(user =>
    user.id === userId
      ? { ...user, ...updates, updatedAt: new Date() }
      : user
  )

  saveUsersToStorage(updatedUsers)
  return updatedUsers.find(user => user.id === userId)
}

// URL 参数处理
function parseQueryParams(url) {
  const params = new URL(url).searchParams
  return Object.fromEntries(params)
}

const params = parseQueryParams('https://example.com?name=John&age=30')
console.log(params) // { name: 'John', age: '30' }

正则表达式增强

// 解析复杂格式
function parseEmailHeader(header) {
  const regex = /(?<name>[^<]+)\s*<?(?<email>[^>\s]+)>?/
  const match = header.match(regex)

  if (match) {
    return {
      name: match.groups.name.trim(),
      email: match.groups.email
    }
  }

  return null
}

console.log(parseEmailHeader('John Doe <john@example.com>'))
// { name: 'John Doe', email: 'john@example.com' }

// dotAll 模式处理多行文本
function extractCodeBlocks(text) {
  const regex = /```[\s\S]*?```/g
  return text.match(regex) || []
}

总结

ES2018 和 ES2019 为 JavaScript 带来了重要改进:

ES2018:

  • 异步迭代 (for-await-of)
  • Promise.prototype.finally()
  • 正则表达式命名捕获组和 dotAll 模式
  • 对象展开运算符
  • 模板字符串转义改进

ES2019:

  • Array.prototype.flat() 和 flatMap()
  • Object.fromEntries()
  • String.prototype.trimStart() 和 trimEnd()
  • Symbol.prototype.description
  • 可选的 catch 绑定

这些新特性让 JavaScript 更加现代化和易用。