Skip to content

ECMAScript 2017(ES8)

Object.values() / Object.entries()

Object.keys() 配套的方法,分别返回对象的值数组和键值对数组。

javascript
{
  const obj = { a: 1, b: 2, c: 3 }

  // Object.keys() - 返回键数组
  console.log(Object.keys(obj)) // ['a', 'b', 'c']

  // Object.values() - 返回值数组
  console.log(Object.values(obj)) // [1, 2, 3]

  // Object.entries() - 返回键值对数组
  console.log(Object.entries(obj)) // [['a', 1], ['b', 2], ['c', 3]]

  // 传统遍历方式
  Object.keys(obj).forEach((key, index) => {
    console.log(key, obj[key])
  })

  // 使用 Object.values() 遍历值
  Object.values(obj).forEach(value => {
    console.log(value) // 1, 2, 3
  })

  // 使用 Object.entries() 遍历键值对
  Object.entries(obj).forEach(([key, value]) => {
    console.log(`${key} is ${value}`)
  })
  // a is 1, b is 2, c is 3

  // 结合 for...of 使用
  for (const value of Object.values(obj)) {
    console.log(value)
  }

  for (const [key, value] of Object.entries(obj)) {
    console.log(`${key}: ${value}`)
  }

  // 实际应用:数据过滤和转换
  const grades = {
    alice: 96,
    bob: 84,
    charlie: 92,
    diana: 88
  }

  // 获取所有分数
  const scores = Object.values(grades)
  console.log(scores) // [96, 84, 92, 88]

  // 过滤高分学生
  const highScorers = Object.entries(grades)
    .filter(([name, score]) => score > 90)
    .map(([name, score]) => name)
  console.log(highScorers) // ['alice', 'charlie']

  // 计算平均分
  const average = Object.values(grades).reduce((sum, score) => sum + score, 0) / Object.values(grades).length
  console.log(average) // 90

  // 对象转换
  const cityTemperatures = {
    'New York': '78/50',
    'London': '64/45',
    'Tokyo': '82/55'
  }

  const temperatureData = Object.entries(cityTemperatures).map(([city, temp]) => {
    const [high, low] = temp.split('/').map(Number)
    return { city, high, low, average: (high + low) / 2 }
  })
  console.log(temperatureData)
  // [
  //   { city: 'New York', high: 78, low: 50, average: 64 },
  //   { city: 'London', high: 64, low: 45, average: 54.5 },
  //   { city: 'Tokyo', high: 82, low: 55, average: 68.5 }
  // ]
}

String.prototype.padStart() / padEnd()

字符串填充方法,在字符串的开头或结尾填充指定字符到目标长度。

javascript
{
  // padStart() - 在开头填充
  console.log('5'.padStart(3, '0'))        // '005'
  console.log('react'.padStart(10))        // '     react'
  console.log('react'.padStart(10, '_'))   // '_____react'

  // padEnd() - 在结尾填充
  console.log('5'.padEnd(3, '0'))          // '500'
  console.log('react'.padEnd(10))          // 'react     '
  console.log('react'.padEnd(10, '_'))     // 'react_____'

  // 实际应用:数字格式化
  for (let i = 1; i <= 12; i++) {
    const month = i.toString().padStart(2, '0')
    console.log(`2023-${month}-01`) // 2023-01-01, 2023-02-01, ...
  }

  // 财务报表对齐
  const amounts = ['0.00', '1,250.50', '15,000.00', '150.75']
  amounts.forEach(amount => {
    console.log(amount.padStart(12))
  })
  // 输出:
  //        0.00
  //    1,250.50
  //   15,000.00
  //      150.75

  // 创建简单的表格
  const data = [
    ['Name', 'Age', 'City'],
    ['Alice', '25', 'New York'],
    ['Bob', '30', 'London'],
    ['Charlie', '35', 'Tokyo']
  ]

  data.forEach(row => {
    const formatted = row.map((cell, index) => {
      const width = index === 0 ? 10 : index === 1 ? 5 : 12
      return cell.padEnd(width)
    }).join('|')
    console.log(formatted)
  })

  // 进度条显示
  function showProgress(current, total, width = 20) {
    const percentage = Math.floor((current / total) * 100)
    const filled = Math.floor((current / total) * width)
    const bar = '█'.repeat(filled).padEnd(width, '░')
    return `[${bar}] ${percentage.toString().padStart(3)}%`
  }

  console.log(showProgress(7, 10))  // [██████████████░░░░░░]  70%
  console.log(showProgress(3, 10))  // [██████░░░░░░░░░░░░░░]  30%

  // 日志格式化
  function formatLog(level, message) {
    const timestamp = new Date().toISOString()
    const formattedLevel = level.padEnd(5)
    return `${timestamp} [${formattedLevel}] ${message}`
  }

  console.log(formatLog('INFO', 'Application started'))
  console.log(formatLog('ERROR', 'Connection failed'))
  // 2023-12-25T10:30:00.000Z [INFO ] Application started
  // 2023-12-25T10:30:01.000Z [ERROR] Connection failed
}

Object.getOwnPropertyDescriptors()

返回对象所有自有属性的属性描述符,用于深度克隆和对象合并。

javascript
{
  // 基本用法
  const obj = {
    name: 'Alice',
    age: 25,
    get info() {
      return `${this.name} is ${this.age} years old`
    },
    set info(value) {
      const [name, age] = value.split(' is ')
      this.name = name
      this.age = parseInt(age)
    }
  }

  const descriptors = Object.getOwnPropertyDescriptors(obj)
  console.log(descriptors)
  /*
  {
    name: { value: 'Alice', writable: true, enumerable: true, configurable: true },
    age: { value: 25, writable: true, enumerable: true, configurable: true },
    info: {
      get: [Function: get info],
      set: [Function: set info],
      enumerable: true,
      configurable: true
    }
  }
  */

  // 深度克隆(包含 getter/setter)
  function deepClone(obj) {
    return Object.create(
      Object.getPrototypeOf(obj),
      Object.getOwnPropertyDescriptors(obj)
    )
  }

  const cloned = deepClone(obj)
  console.log(cloned.info) // 'Alice is 25 years old'
  cloned.name = 'Bob'
  console.log(obj.name)    // 'Alice' (原对象未改变)
  console.log(cloned.info) // 'Bob is 25 years old'

  // 对象合并(保留属性描述符)
  function mergeObjects(target, source) {
    Object.defineProperties(target, Object.getOwnPropertyDescriptors(source))
    return target
  }

  const target = { x: 1 }
  const source = {
    y: 2,
    get z() { return this.y * 2 }
  }

  const merged = mergeObjects(target, source)
  console.log(merged.z) // 4

  // 实际应用:配置对象处理
  const defaultConfig = {
    host: 'localhost',
    port: 3000,
    get url() {
      return `http://${this.host}:${this.port}`
    }
  }

  // 隐藏某些属性
  Object.defineProperty(defaultConfig, 'secret', {
    value: 'hidden-key',
    writable: false,
    enumerable: false,
    configurable: false
  })

  console.log(Object.keys(defaultConfig))                    // ['host', 'port']
  console.log(Object.getOwnPropertyDescriptors(defaultConfig))
  // 显示所有属性描述符,包括不可枚举的

  // 创建不可变对象
  function createImmutable(obj) {
    const descriptors = Object.getOwnPropertyDescriptors(obj)
    
    // 将所有属性设为不可写
    Object.keys(descriptors).forEach(key => {
      if (descriptors[key].value !== undefined) {
        descriptors[key].writable = false
      }
      descriptors[key].configurable = false
    })

    return Object.create(Object.getPrototypeOf(obj), descriptors)
  }

  const immutableObj = createImmutable({ a: 1, b: 2 })
  // immutableObj.a = 999 // 在严格模式下会报错
  console.log(immutableObj.a) // 1
}

Trailing Commas in Function Parameters

函数参数列表和调用中允许尾随逗号。

javascript
{
  // ES8 之前 - 语法错误
  // function oldStyle(a, b, c,) { } // SyntaxError

  // ES8 - 允许尾随逗号
  function newStyle(
    param1,
    param2,
    param3, // 尾随逗号 OK
  ) {
    return param1 + param2 + param3
  }

  // 函数调用中也可以使用
  const result = newStyle(
    1,
    2,
    3, // 尾随逗号 OK
  )

  console.log(result) // 6

  // 实际应用:多行参数定义
  function createUser(
    firstName,
    lastName,
    email,
    age,
    address,
    phoneNumber,
    isActive, // 方便添加新参数
  ) {
    return {
      firstName,
      lastName,
      email,
      age,
      address,
      phoneNumber,
      isActive
    }
  }

  // API 调用示例
  async function fetchUserData(
    userId,
    includeProfile,
    includeSettings,
    includeHistory, // 便于扩展
  ) {
    const params = new URLSearchParams({
      userId,
      includeProfile,
      includeSettings,
      includeHistory,
    })
    
    return fetch(`/api/users?${params}`)
  }

  // 数组和对象中早已支持
  const array = [
    1,
    2,
    3, // OK since ES3
  ]

  const object = {
    a: 1,
    b: 2,
    c: 3, // OK since ES5
  }

  // 有助于版本控制
  // 添加新参数时,git diff 更清晰
  function oldWay(a, b, c) {
    // 添加参数 d 时,需要修改 c 行
  }

  function newWay(
    a,
    b,
    c,
    // 添加 d 只需要新增一行,不影响其他行
  ) {
    // 更好的版本控制体验
  }
}

Async/Await

基于 Promise 的异步编程语法糖,让异步代码看起来像同步代码。

javascript
{
  // Promise 方式
  function fetchUserWithPromise(userId) {
    return fetch(`/api/users/${userId}`)
      .then(response => response.json())
      .then(user => {
        return fetch(`/api/users/${user.id}/posts`)
      })
      .then(response => response.json())
      .then(posts => {
        return { user, posts }
      })
      .catch(error => {
        console.error('Error:', error)
        throw error
      })
  }

  // Async/Await 方式
  async function fetchUserWithAsync(userId) {
    try {
      const userResponse = await fetch(`/api/users/${userId}`)
      const user = await userResponse.json()
      
      const postsResponse = await fetch(`/api/users/${user.id}/posts`)
      const posts = await postsResponse.json()
      
      return { user, posts }
    } catch (error) {
      console.error('Error:', error)
      throw error
    }
  }

  // await 的执行机制
  async function demonstrateAwait() {
    console.log('1. Start')
    
    // await 非 Promise 值
    const value1 = await 42
    console.log('2. Non-promise value:', value1) // 42
    
    // await Promise
    const value2 = await Promise.resolve('Hello')
    console.log('3. Promise value:', value2) // 'Hello'
    
    // await 延迟的 Promise
    const value3 = await new Promise(resolve => {
      setTimeout(() => resolve('Delayed'), 1000)
    })
    console.log('4. Delayed value:', value3) // 'Delayed' (1秒后)
    
    return 'Done'
  }

  // demonstrateAwait().then(result => console.log('5. Final:', result))

  // 错误处理
  async function handleErrors() {
    try {
      const result = await Promise.reject(new Error('Something went wrong'))
    } catch (error) {
      console.log('Caught error:', error.message)
    }

    // 多个 await 的错误处理
    try {
      const user = await fetchUser()
      const posts = await fetchPosts(user.id)
      const comments = await fetchComments(posts)
      return { user, posts, comments }
    } catch (error) {
      // 任何一个 await 失败都会被捕获
      console.error('Pipeline failed:', error)
      return null
    }
  }

  // 并发执行
  async function parallelExecution() {
    // 串行执行 - 较慢
    const start1 = Date.now()
    const result1 = await delay(1000, 'First')
    const result2 = await delay(1000, 'Second')
    console.log(`Serial: ${Date.now() - start1}ms`) // ~2000ms

    // 并行执行 - 较快
    const start2 = Date.now()
    const [result3, result4] = await Promise.all([
      delay(1000, 'Third'),
      delay(1000, 'Fourth')
    ])
    console.log(`Parallel: ${Date.now() - start2}ms`) // ~1000ms
  }

  function delay(ms, value) {
    return new Promise(resolve => setTimeout(() => resolve(value), ms))
  }

  // 实际应用:数据处理管道
  async function processUserData(userId) {
    try {
      // 获取用户基本信息
      const user = await fetchUser(userId)
      console.log(`Processing user: ${user.name}`)

      // 并行获取相关数据
      const [profile, settings, activity] = await Promise.all([
        fetchUserProfile(userId),
        fetchUserSettings(userId),
        fetchUserActivity(userId)
      ])

      // 处理数据
      const processedData = {
        ...user,
        profile: await processProfile(profile),
        settings: normalizeSettings(settings),
        activitySummary: summarizeActivity(activity)
      }

      // 保存结果
      await saveProcessedData(processedData)
      
      return processedData
    } catch (error) {
      console.error(`Failed to process user ${userId}:`, error)
      throw error
    }
  }

  // 模拟异步函数
  async function fetchUser(id) {
    await delay(100)
    return { id, name: `User ${id}`, email: `user${id}@example.com` }
  }

  async function fetchUserProfile(id) {
    await delay(150)
    return { userId: id, bio: 'A great user', avatar: 'avatar.jpg' }
  }

  async function fetchUserSettings(id) {
    await delay(80)
    return { theme: 'dark', notifications: true, language: 'en' }
  }

  async function fetchUserActivity(id) {
    await delay(200)
    return [
      { action: 'login', timestamp: Date.now() },
      { action: 'view_page', timestamp: Date.now() - 1000 }
    ]
  }

  async function processProfile(profile) {
    await delay(50)
    return { ...profile, processed: true }
  }

  function normalizeSettings(settings) {
    return { ...settings, normalized: true }
  }

  function summarizeActivity(activity) {
    return { totalActions: activity.length, lastAction: activity[0] }
  }

  async function saveProcessedData(data) {
    await delay(100)
    console.log('Data saved successfully')
  }

  // 使用示例
  // processUserData(123).then(data => console.log('Final result:', data))
}