跳至内容
ES6 与现代 JavaScript

ES6 与现代 JavaScript

ES6(ECMAScript 2015)是 JavaScript 最重要的一次更新,此后每年发布一个版本(ES2016~ES2025)。本文系统梳理从 ES6 到最新标准的核心语法特性,所有现代浏览器和 Node.js 18+ 均已原生支持。

let / const 与块级作用域

// var 的问题:函数作用域、变量提升、可重复声明
var x = 1
var x = 2  // 不报错,容易出错

// let:块级作用域,不可重复声明,不存在变量提升
let count = 0
if (true) {
  let count = 99  // 独立的块级变量,不影响外部
}
console.log(count)  // 0

// const:常量绑定(引用不可变,对象内部可变)
const PI = 3.14159
const user = { name: "Alice" }
user.name = "Bob"   // ✅ 对象属性可变
// user = {}        // ❌ 引用不能重绑定

箭头函数

箭头函数没有自己的 this,继承外层作用域的 this,解决了传统函数 this 绑定问题。

// 传统函数
const add1 = function(a, b) { return a + b }

// 箭头函数
const add2 = (a, b) => a + b          // 单表达式,隐式返回
const square = x => x * x             // 单参数省略括号
const getObj = () => ({ key: "val" }) // 返回对象字面量需加括号

// this 绑定差异
class Timer {
  constructor() {
    this.count = 0
    // 传统函数:this 是 undefined(严格模式)或 window
    setInterval(function() { this.count++ }, 1000)  // ❌ this 错误

    // 箭头函数:this 继承自 Timer 实例
    setInterval(() => { this.count++ }, 1000)  // ✅ 正确
  }
}

模板字符串

const name = "Alice"
const age = 25

// 传统字符串拼接
const old = "Hello, " + name + "! You are " + age + " years old."

// 模板字符串(反引号):支持插值、多行、表达式
const msg = `Hello, ${name}! You are ${age} years old.`
const expr = `1 + 2 = ${1 + 2}`
const fn = `Result: ${[1,2,3].map(x => x * 2).join(", ")}`

// 多行字符串(不需要 \n)
const html = `
  <div class="card">
    <h2>${name}</h2>
    <p>Age: ${age}</p>
  </div>
`

解构赋值

// 数组解构
const [a, b, c] = [1, 2, 3]
const [first, , third] = [10, 20, 30]   // 跳过元素
const [head, ...tail] = [1, 2, 3, 4]   // rest 元素:tail = [2,3,4]

// 默认值
const [x = 0, y = 0] = [1]  // y = 0

// 对象解构
const { name, age } = { name: "Alice", age: 25 }

// 重命名
const { name: userName, age: userAge = 18 } = user

// 嵌套解构
const { address: { city, zip } } = { address: { city: "Beijing", zip: "100000" } }

// 函数参数解构
function greet({ name, greeting = "Hello" }) {
  return `${greeting}, ${name}!`
}
greet({ name: "Bob" })  // "Hello, Bob!"

// 交换变量
let p = 1, q = 2
;[p, q] = [q, p]  // p=2, q=1

扩展运算符与 Rest

// 扩展运算符(...):展开可迭代对象
const arr1 = [1, 2, 3]
const arr2 = [4, 5, 6]
const merged = [...arr1, ...arr2]       // [1,2,3,4,5,6]
const copy = [...arr1]                  // 浅拷贝

// 展开对象(ES2018)
const base = { x: 1, y: 2 }
const extended = { ...base, z: 3, x: 99 }  // x 被覆盖为 99

// Rest 参数:收集剩余参数
function sum(first, ...rest) {
  return rest.reduce((acc, n) => acc + n, first)
}
sum(1, 2, 3, 4)  // 10

// 函数调用展开
Math.max(...[3, 1, 4, 1, 5, 9])  // 9

对象增强语法

const x = 10, y = 20

// 属性简写(变量名与键名相同时)
const point = { x, y }  // 等价于 { x: x, y: y }

// 方法简写
const obj = {
  name: "Alice",
  greet() { return `Hi, I'm ${this.name}` },     // 简写
  // greet: function() { ... }  // 传统写法
}

// 计算属性名
const key = "dynamic"
const config = {
  [key]: "value",           // { dynamic: "value" }
  [`prefix_${key}`]: 42,   // { prefix_dynamic: 42 }
}

// 对象方法
const o1 = { a: 1 }, o2 = { b: 2 }
Object.assign({}, o1, o2)           // 合并(浅拷贝)
Object.entries({ a: 1, b: 2 })      // [["a",1],["b",2]]
Object.fromEntries([["a",1],["b",2]])  // { a: 1, b: 2 }
Object.keys(obj) / Object.values(obj)
Object.hasOwn(obj, "key")           // ES2022,替代 hasOwnProperty

类(Class)

class Animal {
  #secret = "hidden"  // ES2022 私有字段(#前缀)

  constructor(name) {
    this.name = name
  }

  speak() { return `${this.name} makes a noise.` }

  static create(name) { return new Animal(name) }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name)          // 必须先调用
    this.breed = breed
  }

  speak() {
    return `${super.speak()} Woof!`
  }
}

const d = new Dog("Rex", "Labrador")
console.log(d.speak())          // "Rex makes a noise. Woof!"
console.log(d instanceof Dog)   // true
console.log(d instanceof Animal) // true

Symbol

// 每个 Symbol 都是唯一的
const s1 = Symbol("id")
const s2 = Symbol("id")
console.log(s1 === s2)  // false

// 常用于对象唯一属性键(避免命名冲突)
const ID = Symbol("id")
const user = { [ID]: 123, name: "Alice" }
console.log(user[ID])  // 123(不会被 for...in 和 Object.keys 枚举)

// 内置 Symbol(Well-known Symbols)
class MyArray {
  [Symbol.iterator]() {
    // 自定义迭代行为
  }
}

Map 与 Set

// Map:任意类型为键的键值对集合
const map = new Map()
map.set("key", "value")
map.set({ id: 1 }, "object key")
map.set(42, "number key")

map.get("key")          // "value"
map.has("key")          // true
map.size                // 3
map.delete("key")

// 遍历
for (const [k, v] of map) { console.log(k, v) }
[...map.keys()]
[...map.values()]
[...map.entries()]

// Set:唯一值集合(自动去重)
const set = new Set([1, 2, 2, 3, 3])
console.log([...set])   // [1, 2, 3]

set.add(4)
set.has(1)              // true
set.delete(1)
set.size                // 3

// 数组去重
const unique = [...new Set([1, 1, 2, 3, 3])]  // [1, 2, 3]
// 取交集
const intersection = new Set([...setA].filter(x => setB.has(x)))

Promise 与异步编程

// 创建 Promise
const fetchUser = (id) => new Promise((resolve, reject) => {
  setTimeout(() => {
    if (id > 0) resolve({ id, name: "Alice" })
    else reject(new Error("Invalid ID"))
  }, 1000)
})

// 链式调用
fetchUser(1)
  .then(user => user.name)
  .then(name => console.log(name))
  .catch(err => console.error(err))
  .finally(() => console.log("Done"))

// Promise 并发
Promise.all([fetch("/a"), fetch("/b")])           // 全部成功才 resolve
  .then(([resA, resB]) => { /* ... */ })

Promise.allSettled([fetch("/a"), fetch("/b")])    // 全部完成(不管成败)
  .then(results => results.forEach(r => console.log(r.status)))

Promise.race([fetch("/a"), fetch("/b")])          // 最快的一个
Promise.any([fetch("/a"), fetch("/b")])           // 最快成功的一个(ES2021)

async / await

// async 函数总是返回 Promise
async function loadUserData(userId) {
  try {
    const user = await fetchUser(userId)          // 等待 Promise resolve
    const posts = await fetchPosts(user.id)       // 串行
    return { user, posts }
  } catch (err) {
    console.error("Failed:", err.message)
    throw err                                     // 重新抛出
  } finally {
    console.log("Request completed")
  }
}

// 并行请求(不要在 await 里串行可以并行的操作)
async function loadAll(userId) {
  const [user, config] = await Promise.all([
    fetchUser(userId),
    fetchConfig(),       // 这两个请求同时发出
  ])
  return { user, config }
}

// 顶层 await(ES2022,仅在 ES module 中可用)
const data = await fetch("/api/data").then(r => r.json())

ES Modules

// 具名导出
export const PI = 3.14159
export function add(a, b) { return a + b }
export class User { /* ... */ }

// 默认导出(每个文件只能有一个)
export default class ApiClient { /* ... */ }

// 具名导入
import { PI, add } from "./math.js"

// 默认导入
import ApiClient from "./api-client.js"

// 混合导入
import ApiClient, { PI, add } from "./module.js"

// 重命名
import { add as sum } from "./math.js"

// 命名空间导入
import * as Math from "./math.js"
Math.add(1, 2)

// 动态导入(按需加载,返回 Promise)
const { default: ApiClient } = await import("./api-client.js")

// re-export(转发)
export { add } from "./math.js"
export * from "./utils.js"

可选链与空值合并

const user = {
  profile: {
    address: {
      city: "Beijing"
    }
  }
}

// 可选链 ?.(ES2020):安全地访问深层属性
const city = user?.profile?.address?.city       // "Beijing"
const zip = user?.profile?.address?.zip         // undefined(不报错)
const len = user?.profile?.tags?.length         // undefined

// 调用可能不存在的方法
user?.profile?.notify?.()

// 数组可选链
const first = arr?.[0]

// 空值合并 ??(ES2020):仅在 null/undefined 时取默认值
const name = user?.name ?? "Anonymous"          // 区别于 ||(0/""/false 也会触发 ||)
const count = data?.count ?? 0

// 逻辑赋值(ES2021)
user.name ??= "Default"    // user.name 为 null/undefined 时赋值
user.count ||= 0           // user.count 为 falsy 时赋值
user.count &&= user.count + 1  // user.count 为 truthy 时赋值

数组新方法

const arr = [1, [2, [3, [4]]]]

arr.flat()          // [1, 2, [3, [4]]]    展平一层
arr.flat(2)         // [1, 2, 3, [4]]      展平两层
arr.flat(Infinity)  // [1, 2, 3, 4]        完全展平

arr.flatMap(x => [x, x * 2])  // 先 map 再 flat(1)

// ES2022
[1, 2, 3, 4, 5].at(-1)        // 5(支持负索引)
[1, 2, 3, 4, 5].at(-2)        // 4

// ES2023
[1, 2, 3].findLast(x => x < 3)      // 2(从尾部查找)
[1, 2, 3].findLastIndex(x => x < 3) // 1(返回索引)
[1, 2, 3].toReversed()  // 不修改原数组的反转(返回新数组)
[3, 1, 2].toSorted()    // 不修改原数组的排序
[1, 2, 3].toSpliced(1, 1, 9)  // 不修改原数组的 splice
[1, 2, 3].with(1, 99)  // 返回 [1, 99, 3],不修改原数组

错误处理

// 标准错误类型
try {
  null.property       // TypeError
  undeclaredVar       // ReferenceError
  JSON.parse("{bad}") // SyntaxError
} catch (err) {
  if (err instanceof TypeError) {
    console.error("类型错误:", err.message)
  } else {
    throw err  // 重新抛出未知错误
  }
}

// 自定义错误
class ApiError extends Error {
  constructor(message, statusCode) {
    super(message)
    this.name = "ApiError"
    this.statusCode = statusCode
  }
}

// Error cause(ES2022):链式错误
try {
  await fetch("/api")
} catch (err) {
  throw new Error("Failed to load data", { cause: err })
}

// 可选的 catch 绑定(ES2019):不需要使用 err 时
try { /* ... */ } catch { /* 忽略错误 */ }

Proxy 与 Reflect

// Proxy:拦截对象的基本操作
const handler = {
  get(target, key) {
    console.log(`读取属性 ${String(key)}`)
    return Reflect.get(target, key)
  },
  set(target, key, value) {
    if (typeof value !== "number") throw new TypeError("只允许数字")
    return Reflect.set(target, key, value)
  }
}

const data = new Proxy({}, handler)
data.count = 1   // 触发 set
data.count       // 触发 get → 1
// data.name = "Alice"  // ❌ TypeError

// 常见应用:响应式系统(Vue 3 的响应式基于 Proxy)、数据校验、访问日志
最后更新于