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)、数据校验、访问日志
最后更新于