概述
ES6(ECMAScript 2015)是JavaScript历史上最重要的一次更新,后续的ES7-ES14(ES2016-ES2023)继续引入了大量新特性。掌握这些现代JavaScript特性是成为一名高效前端开发者的必备条件。
本文将系统介绍最实用的ES6+特性,包括语法变化、新的数据结构、异步编程模式和现代代码组织方式。
let 和 const — 块级作用域
ES6引入了 let 和 const 两个新的变量声明方式,取代 var 的大部分使用场景。
// var 的问题:函数作用域,存在变量提升
var x = 10;
if (true) {
var x = 20; // 覆盖了外部的 x
}
console.log(x); // 20
// let:块级作用域
let y = 10;
if (true) {
let y = 20; // 块级作用域内的新变量
}
console.log(y); // 10
// const:块级作用域 + 不可重新赋值
const z = 10;
// z = 20; // TypeError!
// const 对象/数组仍可修改属性
const user = { name: '张三' };
user.name = '李四'; // 可以修改
// user = {}; // TypeError! 不能重新赋值
最佳实践:默认使用const,只在需要重新赋值时使用let,不再使用var。
箭头函数
箭头函数提供了更简洁的函数语法,并且自动绑定外层的 this。
// 传统函数
function add(a, b) {
return a + b;
}
// 箭头函数
const add = (a, b) => a + b;
// 单参数可省略括号
const double = x => x * 2;
// 多行箭头函数
const processUser = (user) => {
const name = user.name.toUpperCase();
const age = user.age + 1;
return { name, age };
};
// 箭头函数最大的优势:this 绑定
class Timer {
constructor() {
this.seconds = 0;
}
start() {
// 箭头函数继承外层 this
setInterval(() => {
this.seconds++;
console.log(this.seconds);
}, 1000);
}
}
// 数组方法中的箭头函数
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(n => n * 2);
const evens = numbers.filter(n => n % 2 === 0);
const sum = numbers.reduce((acc, n) => acc + n, 0);
模板字符串
模板字符串使用反引号(`` ` ``),支持多行文本和变量插值。
const name = '张三';
const age = 25;
// 变量插值
const greeting = `你好,${name}!你今年 ${age} 岁。`;
// 多行字符串
const html = `
<div class="user-card">
<h2>${name}</h2>
<p>年龄:${age}</p>
<p>明年你 ${age + 1} 岁</p>
</div>
`;
// 标签模板字符串
function highlight(strings, ...values) {
return strings.reduce((result, str, i) => {
const value = values[i - 1] ? `<mark>${values[i - 1]}</mark>` : '';
return result + str + value;
}, '');
}
const name2 = 'JavaScript';
const result = highlight`Hello ${name2}, welcome!`;
// "Hello <mark>JavaScript</mark>, welcome!"
解构赋值
解构赋值让你可以从数组或对象中快速提取值到变量中。
数组解构
// 基本解构
const [a, b, c] = [1, 2, 3];
// a=1, b=2, c=3
// 跳过元素
const [first, , third] = [1, 2, 3];
// first=1, third=3
// 剩余元素
const [head, ...tail] = [1, 2, 3, 4];
// head=1, tail=[2, 3, 4]
// 默认值
const [x = 0, y = 0] = [5];
// x=5, y=0
// 交换变量
let m = 1, n = 2;
[m, n] = [n, m]; // m=2, n=1
对象解构
// 基本解构
const user = { name: '张三', age: 25, city: '北京' };
const { name, age } = user;
// name='张三', age=25
// 重命名
const { name: userName, age: userAge } = user;
// 默认值
const { name, gender = '未知' } = user;
// gender='未知'
// 嵌套解构
const data = {
result: {
users: [{ name: '张三' }]
}
};
const { result: { users: [firstUser] } } = data;
// 函数参数解构
function createUser({ name, age, role = 'user' }) {
return { name, age, role };
}
createUser({ name: '张三', age: 25 });
展开运算符和剩余参数
// 展开数组
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5]; // [1, 2, 3, 4, 5]
// 展开对象
const user = { name: '张三', age: 25 };
const updatedUser = { ...user, age: 26, city: '北京' };
// 浅拷贝
const copy = [...arr1];
const objCopy = { ...user };
// 剩余参数
function sum(...numbers) {
return numbers.reduce((acc, n) => acc + n, 0);
}
sum(1, 2, 3, 4); // 10
// 解构 + 剩余
const [first, ...rest] = [1, 2, 3, 4];
// first=1, rest=[2, 3, 4]
const { id, ...userData } = { id: 1, name: '张三', age: 25 };
// id=1, userData={ name: '张三', age: 25 }
Promise 和 async/await
异步编程是JavaScript的核心概念,ES6的Promise和后续的async/await极大改善了异步代码的可读性。
// Promise 基础
function fetchUser(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id > 0) {
resolve({ id, name: '张三' });
} else {
reject(new Error('无效ID'));
}
}, 1000);
});
}
// Promise 链
fetchUser(1)
.then(user => fetchUserPosts(user.id))
.then(posts => console.log(posts))
.catch(error => console.error(error))
.finally(() => console.log('完成'));
// async/await — 更优雅的写法
async function loadUserData(id) {
try {
const user = await fetchUser(id);
const posts = await fetchUserPosts(user.id);
return { user, posts };
} catch (error) {
console.error('加载失败:', error);
throw error;
}
}
// 并行执行多个异步操作
async function loadDashboard() {
const [user, posts, notifications] = await Promise.all([
fetchUser(1),
fetchUserPosts(1),
fetchNotifications(1)
]);
return { user, posts, notifications };
}
// Promise.allSettled — 不管成功失败都返回
const results = await Promise.allSettled([
fetchUser(1),
fetchUserPosts(1),
]);
// results: [{ status: 'fulfilled', value: ... }, { status: 'rejected', reason: ... }]
ES Module 模块系统
// math.js — 导出
export const PI = 3.14159;
export function add(a, b) { return a + b; }
export default class Calculator { /* ... */ }
// app.js — 导入
import Calculator, { PI, add } from './math.js';
import * as math from './math.js';
// 动态导入(按需加载)
async function loadChart() {
const { Chart } = await import('./chart.js');
return new Chart();
}
// 导出重命名
export { add as sum } from './math.js';
可选链和空值合并
// 可选链 ?. — 安全访问嵌套属性
const user = {
profile: {
address: {
city: '北京'
}
}
};
// 传统方式
const city1 = user && user.profile && user.profile.address && user.profile.address.city;
// 可选链
const city2 = user?.profile?.address?.city; // '北京'
const zip = user?.profile?.address?.zip; // undefined(不报错)
// 可选链调用方法
const result = user?.getName?.();
// 可选链访问数组
const first = user?.tags?.[0];
// 空值合并 ?? — 仅在 null/undefined 时使用默认值
const name = null ?? '默认名'; // '默认名'
const name2 = '' ?? '默认名'; // ''(空字符串不是null/undefined)
const name3 = 0 ?? '默认名'; // 0(0不是null/undefined)
// 对比 || 运算符
const name4 = '' || '默认名'; // '默认名'(空字符串是 falsy)
const name5 = 0 || '默认名'; // '默认名'(0是 falsy)
Map 和 Set
// Map — 键值对集合,键可以是任意类型
const map = new Map();
map.set('name', '张三');
map.set(42, '数字键');
map.set(true, '布尔键');
map.get('name'); // '张三'
map.has(42); // true
map.size; // 3
map.delete(true);
// 遍历
for (const [key, value] of map) {
console.log(key, value);
}
// Set — 唯一值集合
const set = new Set([1, 2, 3, 2, 1]);
set.size; // 3
set.has(2); // true
// 去重
const arr = [1, 1, 2, 3, 3, 4];
const unique = [...new Set(arr)]; // [1, 2, 3, 4]
// 集合运算
const a = new Set([1, 2, 3]);
const b = new Set([2, 3, 4]);
const union = new Set([...a, ...b]); // 并集
const intersection = new Set([...a].filter(x => b.has(x))); // 交集
const difference = new Set([...a].filter(x => !b.has(x))); // 差集
其他实用特性
Object.entries / Object.keys / Object.values
const user = { name: '张三', age: 25, city: '北京' };
Object.keys(user); // ['name', 'age', 'city']
Object.values(user); // ['张三', 25, '北京']
Object.entries(user); // [['name', '张三'], ['age', 25], ['city', '北京']]
// 转换为 Map
const map = new Map(Object.entries(user));
Array 方法增强
const numbers = [1, 2, 3, 4, 5];
// find / findIndex
const found = numbers.find(n => n > 3); // 4
const index = numbers.findIndex(n => n > 3); // 3
// includes
numbers.includes(3); // true
// flat / flatMap
const nested = [[1, 2], [3, [4, 5]]];
nested.flat(); // [1, 2, 3, [4, 5]]
nested.flat(Infinity); // [1, 2, 3, 4, 5]
const sentences = ['Hello World', 'Good Morning'];
sentences.flatMap(s => s.split(' '));
// ['Hello', 'World', 'Good', 'Morning']
对象合并
// Object.assign(浅合并)
const target = { a: 1, b: 2 };
Object.assign(target, { b: 3, c: 4 });
// target: { a: 1, b: 3, c: 4 }
// 展开运算符(推荐)
const merged = { ...target, b: 3, c: 4 };
// structuredClone(深拷贝)
const original = { nested: { value: 1 } };
const deepCopy = structuredClone(original);
总结:现代JavaScript最佳实践
- 使用
const优先,let次之,避免var - 使用箭头函数进行回调,传统函数用于方法定义
- 使用模板字符串替代字符串拼接
- 使用解构赋值简化数据提取
- 使用
async/await替代 Promise 链 - 使用 ES Module 组织代码
- 使用可选链
?.和空值合并?? - 使用
Map/Set替代普通对象作为集合 - 使用
...rest/spread简化参数和展开操作