Skip to content

ECMAScript 2018(ES9)

RegExp Features

dotAll flag (s)

s 标志使 . 能够匹配任何字符,包括换行符。

javascript
{
  // 传统情况下,. 不能匹配换行符
  console.log(/foo.bar/.test('fooabar'))  // true
  console.log(/foo.bar/.test('foo\nbar')) // false

  // 使用 dotAll 标志
  console.log(/foo.bar/s.test('foo\nbar')) // true

  // 检查标志
  const regex = /foo.bar/gisu
  console.log(regex.dotAll) // true
  console.log(regex.flags)  // 'gisu'

  // 实际应用:多行文本解析
  const htmlContent = `
    <div class="header">
      Title
    </div>
  `

  // 匹配跨行的 HTML 标签
  const tagRegex = /<div.*?>.*?<\/div>/s
  console.log(tagRegex.test(htmlContent)) // true

  // 解析配置文件中的多行值
  const config = `
    name = MyApp
    description = This is a long
    description that spans
    multiple lines
    version = 1.0.0
  `

  const multiLineValue = /description\s*=\s*(.*?)(?=\n\w+\s*=|\n*$)/s
  const match = config.match(multiLineValue)
  if (match) {
    console.log(match[1].trim().replace(/\s+/g, ' '))
    // 'This is a long description that spans multiple lines'
  }
}

Named Capture Groups

使用命名捕获组让正则表达式更可读和可维护。

javascript
{
  // 传统的数字索引方式
  const dateMatch = '2020-03-28'.match(/(\d{4})-(\d{2})-(\d{2})/)
  console.log(dateMatch[1]) // '2020'
  console.log(dateMatch[2]) // '03'
  console.log(dateMatch[3]) // '28'

  // 命名捕获组
  const namedMatch = '2020-03-28'.match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/)
  console.log(namedMatch.groups.year)  // '2020'
  console.log(namedMatch.groups.month) // '03'
  console.log(namedMatch.groups.day)   // '28'

  // 实际应用:解析电子邮件
  const email = 'john.doe@example.com'
  const emailRegex = /(?<username>[^@]+)@(?<domain>[^.]+)\.(?<tld>.+)/
  const emailMatch = email.match(emailRegex)
  
  if (emailMatch) {
    const { username, domain, tld } = emailMatch.groups
    console.log({ username, domain, tld })
    // { username: 'john.doe', domain: 'example', tld: 'com' }
  }

  // 解析 URL
  const url = 'https://api.github.com/users/octocat'
  const urlRegex = /(?<protocol>https?):\/\/(?<host>[^\/]+)(?<path>\/.*)?/
  const urlMatch = url.match(urlRegex)
  
  if (urlMatch) {
    console.log(urlMatch.groups)
    // { protocol: 'https', host: 'api.github.com', path: '/users/octocat' }
  }

  // 在替换中使用命名组
  const text = 'Call me at 555-123-4567'
  const phoneRegex = /(?<area>\d{3})-(?<exchange>\d{3})-(?<number>\d{4})/
  const formatted = text.replace(phoneRegex, '($<area>) $<exchange>-$<number>')
  console.log(formatted) // 'Call me at (555) 123-4567'

  // 解构赋值与命名组
  function parseLogEntry(logLine) {
    const logRegex = /(?<timestamp>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) \[(?<level>\w+)\] (?<message>.*)/
    const match = logLine.match(logRegex)
    
    if (match) {
      return match.groups
    }
    return null
  }

  const log = '2023-12-25 10:30:45 [ERROR] Database connection failed'
  console.log(parseLogEntry(log))
  // { timestamp: '2023-12-25 10:30:45', level: 'ERROR', message: 'Database connection failed' }
}

Lookbehind Assertions

正向和负向回顾断言,检查某个位置之前的内容。

javascript
{
  const text = 'hello world'

  // 正向前瞻 (?=) - 匹配后面跟着指定内容的位置
  console.log(text.match(/hello(?=\sworld)/)) // ['hello']

  // 正向回顾 (?<=) - 匹配前面是指定内容的位置
  console.log(text.match(/(?<=hello\s)world/)) // ['world']

  // 负向回顾 (?<!) - 匹配前面不是指定内容的位置
  console.log(text.match(/(?<!helle\s)world/)) // ['world']

  // 实际应用:价格解析
  const prices = '$100 €200 ¥300 100USD'
  
  // 匹配前面有货币符号的数字
  const withSymbol = prices.match(/(?<=[$€¥])\d+/g)
  console.log(withSymbol) // ['100', '200', '300']

  // 匹配后面有货币代码的数字
  const withCode = prices.match(/\d+(?=[A-Z]{3})/g)
  console.log(withCode) // ['100']

  // 匹配不在引号内的单词
  const code = 'const message = "hello world"; const name = "Alice"'
  const outsideQuotes = code.match(/(?<!")(?<!\w)\w+(?=\s*=)/g)
  console.log(outsideQuotes) // ['const', 'const']

  // 密码验证:至少8位,包含大小写字母和数字
  function validatePassword(password) {
    const hasLower = /(?=.*[a-z])/.test(password)
    const hasUpper = /(?=.*[A-Z])/.test(password)
    const hasDigit = /(?=.*\d)/.test(password)
    const isLongEnough = password.length >= 8

    return hasLower && hasUpper && hasDigit && isLongEnough
  }

  console.log(validatePassword('Password123')) // true
  console.log(validatePassword('password'))    // false

  // 提取 HTML 标签中的属性值
  const html = '<img src="image.jpg" alt="A beautiful sunset" width="300">'
  const attributeRegex = /(?<=\w+=")[^"]+(?=")/g
  const attributes = html.match(attributeRegex)
  console.log(attributes) // ['image.jpg', 'A beautiful sunset', '300']
}

Asynchronous Iteration

for-await-of

异步迭代器,用于处理异步可迭代对象。

javascript
{
  // 创建异步生成器
  async function* asyncGenerator() {
    yield await Promise.resolve(1)
    yield await Promise.resolve(2)
    yield await Promise.resolve(3)
  }

  // 使用 for-await-of 循环
  async function consumeAsyncGenerator() {
    for await (const value of asyncGenerator()) {
      console.log(value) // 1, 2, 3 (按顺序输出)
    }
  }

  // 实际应用:处理异步数据流
  function createAsyncIterable(urls) {
    return {
      [Symbol.asyncIterator]() {
        let index = 0
        return {
          async next() {
            if (index < urls.length) {
              try {
                const response = await fetch(urls[index++])
                const data = await response.json()
                return { value: data, done: false }
              } catch (error) {
                return { value: error, done: false }
              }
            } else {
              return { done: true }
            }
          }
        }
      }
    }
  }

  // 使用示例
  async function fetchMultipleAPIs() {
    const urls = ['/api/users', '/api/posts', '/api/comments']
    const asyncIterable = createAsyncIterable(urls)

    for await (const result of asyncIterable) {
      if (result instanceof Error) {
        console.error('Failed to fetch:', result.message)
      } else {
        console.log('Fetched data:', result)
      }
    }
  }

  // 自定义异步迭代器示例
  const timeoutIterable = {
    [Symbol.asyncIterator]() {
      let count = 0
      return {
        async next() {
          if (count < 3) {
            await new Promise(resolve => setTimeout(resolve, 1000))
            return { value: `Item ${++count}`, done: false }
          }
          return { done: true }
        }
      }
    }
  }

  async function processTimeouts() {
    console.log('Starting async iteration...')
    for await (const item of timeoutIterable) {
      console.log(item) // 每秒输出一次
    }
    console.log('Completed!')
  }

  // 处理 ReadableStream
  async function processStream(stream) {
    const reader = stream.getReader()
    try {
      for await (const chunk of readableStreamAsyncIterable(reader)) {
        console.log('Chunk:', chunk)
      }
    } finally {
      reader.releaseLock()
    }
  }

  async function* readableStreamAsyncIterable(reader) {
    try {
      while (true) {
        const { done, value } = await reader.read()
        if (done) return
        yield value
      }
    } finally {
      reader.releaseLock()
    }
  }
}

Object Rest/Spread Properties

对象的剩余/扩展属性,类似于数组的扩展语法。

javascript
{
  // 对象扩展
  const obj1 = { a: 1, b: 2 }
  const obj2 = { c: 3, d: 4 }
  
  const combined = { ...obj1, ...obj2, e: 5 }
  console.log(combined) // { a: 1, b: 2, c: 3, d: 4, e: 5 }

  // 对象剩余属性
  const person = {
    name: 'Alice',
    age: 30,
    city: 'New York',
    country: 'USA',
    occupation: 'Developer'
  }

  const { name, age, ...rest } = person
  console.log(name) // 'Alice'
  console.log(age)  // 30
  console.log(rest) // { city: 'New York', country: 'USA', occupation: 'Developer' }

  // 实际应用:配置对象合并
  const defaultConfig = {
    theme: 'light',
    language: 'en',
    notifications: true,
    autoSave: false
  }

  const userConfig = {
    theme: 'dark',
    notifications: false
  }

  const finalConfig = { ...defaultConfig, ...userConfig }
  console.log(finalConfig)
  // { theme: 'dark', language: 'en', notifications: false, autoSave: false }

  // 函数参数解构
  function updateUser({ id, ...updates }) {
    console.log(`Updating user ${id} with:`, updates)
    // 模拟数据库更新
    return { id, ...updates, updatedAt: new Date() }
  }

  const user = updateUser({
    id: 123,
    name: 'Bob',
    email: 'bob@example.com',
    age: 25
  })
  console.log(user)

  // 过滤对象属性
  function omit(obj, keys) {
    const { [keys]: _, ...rest } = obj
    return rest
  }

  function omitMultiple(obj, keys) {
    const result = { ...obj }
    keys.forEach(key => delete result[key])
    return result
  }

  const data = { a: 1, b: 2, c: 3, d: 4 }
  console.log(omitMultiple(data, ['b', 'd'])) // { a: 1, c: 3 }

  // 条件性属性添加
  function createUser(name, email, isAdmin = false) {
    return {
      name,
      email,
      createdAt: new Date(),
      ...(isAdmin && { role: 'admin', permissions: ['read', 'write', 'delete'] })
    }
  }

  console.log(createUser('Alice', 'alice@example.com'))
  // { name: 'Alice', email: 'alice@example.com', createdAt: Date }

  console.log(createUser('Bob', 'bob@example.com', true))
  // { name: 'Bob', email: 'bob@example.com', createdAt: Date, role: 'admin', permissions: [...] }

  // 深度克隆(浅层)
  function shallowClone(obj) {
    return { ...obj }
  }

  // 对象数组的去重
  function uniqueBy(array, key) {
    const seen = new Set()
    return array.filter(item => {
      const value = item[key]
      if (seen.has(value)) {
        return false
      }
      seen.add(value)
      return true
    })
  }

  const users = [
    { id: 1, name: 'Alice' },
    { id: 2, name: 'Bob' },
    { id: 1, name: 'Alice Duplicate' }
  ]

  console.log(uniqueBy(users, 'id'))
  // [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]
}

Promise.prototype.finally()

无论 Promise 成功或失败都会执行的回调。

javascript
{
  // 基本用法
  function fetchData() {
    return fetch('/api/data')
      .then(response => response.json())
      .then(data => {
        console.log('Data received:', data)
        return data
      })
      .catch(error => {
        console.error('Error:', error)
        throw error
      })
      .finally(() => {
        console.log('Request completed') // 无论成功失败都会执行
      })
  }

  // 实际应用:加载状态管理
  class DataLoader {
    constructor() {
      this.isLoading = false
      this.data = null
      this.error = null
    }

    async loadData(url) {
      this.isLoading = true
      this.error = null

      try {
        const response = await fetch(url)
        if (!response.ok) {
          throw new Error(`HTTP ${response.status}: ${response.statusText}`)
        }
        this.data = await response.json()
        return this.data
      } catch (error) {
        this.error = error
        throw error
      } finally {
        this.isLoading = false // 无论成功失败都重置加载状态
      }
    }
  }

  // 资源清理示例
  async function processFile(filename) {
    const file = await openFile(filename)
    
    try {
      const data = await parseFile(file)
      return await processData(data)
    } catch (error) {
      console.error('Processing failed:', error)
      throw error
    } finally {
      await closeFile(file) // 确保文件始终被关闭
    }
  }

  // 模拟网络请求的工具函数
  function simulateRequest(shouldSucceed = true, delay = 1000) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (shouldSucceed) {
          resolve({ data: 'Success', timestamp: Date.now() })
        } else {
          reject(new Error('Request failed'))
        }
      }, delay)
    })
  }

  // 使用 finally 进行日志记录
  async function requestWithLogging(url) {
    const startTime = Date.now()
    
    try {
      const result = await simulateRequest(Math.random() > 0.5)
      console.log('Request succeeded:', result)
      return result
    } catch (error) {
      console.error('Request failed:', error.message)
      throw error
    } finally {
      const duration = Date.now() - startTime
      console.log(`Request to ${url} completed in ${duration}ms`)
    }
  }

  // 批量请求处理
  async function batchProcess(tasks) {
    const results = []
    let completed = 0
    
    for (const task of tasks) {
      try {
        const result = await task()
        results.push({ success: true, data: result })
      } catch (error) {
        results.push({ success: false, error: error.message })
      } finally {
        completed++
        console.log(`Progress: ${completed}/${tasks.length}`)
      }
    }
    
    return results
  }

  // 示例使用
  // requestWithLogging('/api/users')
}