ES6+ 常用知识点完整指南 | JavaScript ES2015+ 核心特性详解
📚 ES6+ 简介
ES6(ECMAScript 2015)是 JavaScript 语言的一次重大升级,引入了许多新特性,让代码更简洁、更强大、更易维护。本文将系统性地介绍 ES6+ 的常用知识点。
💡 阅读建议
- 本文涵盖 ES6+ 所有核心特性,建议收藏以便随时查阅
- 每个章节都配有详细代码示例,可直接在浏览器控制台运行
- 标注了 ✅ 推荐和 ❌ 不推荐的实践,帮助你避坑
- 适合有 JavaScript 基础的开发者深入学习
📖 本文目录
本文将深入讲解以下核心主题:
- 变量声明 - let、const 的特性和最佳实践
- 解构赋值 - 数组和对象的解构技巧
- 可选链与空值合并 - 安全访问深层对象属性
- 函数增强 - 箭头函数、默认参数、剩余参数
- 扩展运算符 - 数组和对象的高效操作
- Promise 与 Async/Await - 现代异步编程解决方案
- 模块化 - ES6 模块系统完整指南
- 实用工具函数 - 常用算法和设计模式实现
🔤 变量声明
let - 块级作用域变量
推荐使用 let
关键字替代 var
关键字声明变量。var
存在以下问题:
1. 作用域穿透(越域)
var
声明的变量会穿透块级作用域,而 let
具有块级作用域:
{
var a = 1;
let b = 2;
}
console.log(a); // 1 - var 穿透了块级作用域
console.log(b); // ReferenceError: b is not defined - let 受块级作用域限制
应用场景: 在 for
循环中使用 let
可以避免闭包问题:
// 使用 var 的问题
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 输出: 3 3 3
}
// 使用 let 的正确方式
for (let j = 0; j < 3; j++) {
setTimeout(() => console.log(j), 100); // 输出: 0 1 2
}
2. 重复声明问题
// var 可以声明多次(容易导致变量被意外覆盖)
var m = 1;
var m = 2;
console.log(m); // 2
// let 只能声明一次(更安全)
let n = 3;
// let n = 4; // SyntaxError: Identifier 'n' has already been declared
console.log(n); // 3
3. 变量提升问题
// var 存在变量提升(可能导致意外的 undefined)
console.log(x); // undefined(而不是报错)
var x = 10;
// let 不存在变量提升(形成暂时性死区)
console.log(y); // ReferenceError: Cannot access 'y' before initialization
let y = 20;
💡 最佳实践
- 优先使用
const
声明常量和不会改变的变量 - 需要重新赋值时使用
let
声明变量 - 完全避免使用
var
,它存在作用域和变量提升等问题
⚠️ var 的问题
var
存在作用域穿透、变量提升、可重复声明等问题,在现代 JavaScript 开发中应该避免使用。使用 let
和 const
可以避免这些潜在的 bug。
const - 常量声明
const
用于声明常量,具有以下特点:
// 1. 声明之后不允许重新赋值
const PI = 3.14159;
// PI = 3.14; // TypeError: Assignment to constant variable
// 2. 声明时必须初始化
// const MAX_SIZE; // SyntaxError: Missing initializer in const declaration
const MAX_SIZE = 100;
// 3. 对象/数组的属性可以修改(引用地址不变)
const person = { name: 'Jack' };
person.name = 'Tom'; // ✅ 允许修改属性
person.age = 25; // ✅ 允许添加属性
// person = {}; // ❌ 不允许重新赋值
const arr = [1, 2, 3];
arr.push(4); // ✅ 允许修改数组内容
// arr = []; // ❌ 不允许重新赋值
💡 const 使用建议
- ✅ 配置常量、不可变值使用
const
- ✅ API 地址、魔法数字等使用
const
- ✅ 需要完全不可变的对象,考虑使用
Object.freeze()
- ✅ 默认使用
const
,除非确实需要重新赋值
🎯 解构赋值
解构赋值是一种便捷的提取数组或对象中值的方式。
数组解构
// 基本用法
let arr = [1, 2, 3];
const [x, y, z] = arr;
console.log(x, y, z); // 1 2 3
// 跳过某些值
const [first, , third] = arr;
console.log(first, third); // 1 3
// 使用剩余运算符
const [head, ...tail] = [1, 2, 3, 4, 5];
console.log(head); // 1
console.log(tail); // [2, 3, 4, 5]
// 设置默认值
const [a = 0, b = 0] = [1];
console.log(a, b); // 1 0
// 交换变量(无需临时变量)
let num1 = 10, num2 = 20;
[num1, num2] = [num2, num1];
console.log(num1, num2); // 20 10
// 实际应用:从函数返回多个值
function getMinMax(arr) {
return [Math.min(...arr), Math.max(...arr)];
}
const [min, max] = getMinMax([3, 1, 4, 1, 5, 9]);
console.log(`最小值: ${min}, 最大值: ${max}`); // 最小值: 1, 最大值: 9
// 解析 URL 参数
const url = 'https://example.com/page?name=Tom&age=25';
const [, params] = url.split('?');
console.log(params); // name=Tom&age=25
对象解构
const person = {
name: "Jack",
age: 21,
language: ['Java', 'JavaScript', 'CSS']
};
// 基本解构
const { name, age, language } = person;
console.log(name); // Jack
console.log(age); // 21
console.log(language); // ['Java', 'JavaScript', 'CSS']
// 重命名变量
const { name: userName, age: userAge } = person;
console.log(userName); // Jack
console.log(userAge); // 21
// 设置默认值
const { name, gender = 'male' } = person;
console.log(gender); // male
// 嵌套解构
const user = {
id: 1,
info: {
email: '[email protected]',
address: {
city: 'Beijing'
}
}
};
const { info: { email, address: { city } } } = user;
console.log(email); // [email protected]
console.log(city); // Beijing
// 函数参数解构
function printUser({ name, age = 18 }) {
console.log(`${name} is ${age} years old`);
}
printUser({ name: 'Tom', age: 25 }); // Tom is 25 years old
printUser({ name: 'Jerry' }); // Jerry is 18 years old
// 实际应用:处理 API 响应数据
const apiResponse = {
code: 200,
data: {
userInfo: {
id: 1,
name: 'Tom',
avatar: 'avatar.jpg'
},
permissions: ['read', 'write']
}
};
const {
code,
data: {
userInfo: { name: userName, avatar },
permissions: [readPerm, writePerm]
}
} = apiResponse;
console.log(userName); // Tom
console.log(avatar); // avatar.jpg
console.log(readPerm); // read
// React/Vue 组件中的应用
function UserCard({ user: { name, age, email } }) {
return `<div>${name} - ${email}</div>`;
}
🔗 可选链操作符
链判断运算符(?.)
在访问深层嵌套的对象属性时,需要进行繁琐的安全检查。ES2020 引入的可选链操作符 ?.
极大地简化了这一过程。
📌 浏览器兼容性
可选链操作符 ?.
是 ES2020 特性,在现代浏览器中已被广泛支持:
- Chrome 80+
- Firefox 74+
- Safari 13.1+
- Edge 80+
旧版浏览器需要使用 Babel 等工具进行转译。
let message = null;
// ❌ 错误的写法(会抛出异常)
// const firstName = message.body.user.firstName || 'default';
// ⚠️ 传统的安全写法(过于繁琐)
const firstName = (message
&& message.body
&& message.body.user
&& message.body.user.firstName) || 'default';
// ✅ 使用可选链(简洁优雅)
const firstName2 = message?.body?.user?.firstName || 'default';
console.log(firstName2); // default
高级用法:
// 可选的函数调用
const obj = {
func: () => 'hello'
};
console.log(obj.func?.()); // hello
console.log(obj.func2?.()); // undefined(不会报错)
// 可选的数组索引
const arr = null;
console.log(arr?.[0]); // undefined
// 实际应用示例
const user = {
profile: {
settings: {
theme: 'dark'
}
}
};
// 安全访问深层属性
const theme = user?.profile?.settings?.theme ?? 'light';
console.log(theme); // dark
空值合并运算符(??)
??
运算符用于判断值是否为 null
或 undefined
,与 ||
的区别在于它不会将 0
、''
、false
视为假值。这在处理数字 0、空字符串等有效值时非常有用:
const value1 = 0 || 'default'; // 'default'(0 被视为假值)
const value2 = 0 ?? 'default'; // 0(0 不是 null/undefined)
const value3 = '' || 'default'; // 'default'
const value4 = '' ?? 'default'; // ''
const value5 = false || 'default'; // 'default'
const value6 = false ?? 'default'; // false
// 实际应用:配置项默认值
function initConfig(options) {
// 使用 ?? 确保 0 是有效值
const timeout = options.timeout ?? 3000;
const retryCount = options.retryCount ?? 0; // 0 次重试是有效配置
const enableCache = options.enableCache ?? true;
return { timeout, retryCount, enableCache };
}
console.log(initConfig({ timeout: 0 })); // { timeout: 0, retryCount: 0, enableCache: true }
🎨 函数增强
参数默认值
// ⚠️ ES6 之前的变通写法
function add(a, b) {
b = b || 1; // 问题:当 b = 0 时会使用默认值
return a + b;
}
console.log(add(10)); // 11
console.log(add(10, 0)); // 11(期望是 10)
// ✅ ES6 默认参数(更准确)
function add2(a, b = 1) {
return a + b;
}
console.log(add2(10)); // 11
console.log(add2(10, 0)); // 10
// 默认参数可以是表达式
function getDefaultValue() {
console.log('计算默认值');
return 5;
}
function multiply(a, b = getDefaultValue()) {
return a * b;
}
multiply(10); // 打印 "计算默认值",返回 50
multiply(10, 2); // 不会执行 getDefaultValue(),返回 20
// 默认参数可以引用其他参数
function createUser(name, role = 'user', greeting = `Hello, ${name}!`) {
return { name, role, greeting };
}
console.log(createUser('Tom'));
// { name: 'Tom', role: 'user', greeting: 'Hello, Tom!' }
剩余参数(Rest Parameters)
// 收集多余的参数
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3, 4, 5)); // 15
// 结合普通参数使用
function multiply(multiplier, ...numbers) {
return numbers.map(num => num * multiplier);
}
console.log(multiply(2, 1, 2, 3)); // [2, 4, 6]
箭头函数
箭头函数提供了更简洁的函数写法,并且不绑定自己的 this
。
// 传统函数
var print = function (obj) {
console.log(obj);
}
// ✅ 箭头函数简化
const print2 = obj => console.log(obj);
print2(100); // 100
// 无参数
const greet = () => console.log('Hello');
// 单个参数(可省略括号)
const double = num => num * 2;
// 多个参数
const sum = (a, b) => a + b;
console.log(sum(10, 10)); // 20
// 多行语句需要使用花括号
const sum3 = (a, b) => {
const c = a + b;
return c;
};
console.log(sum3(10, 20)); // 30
// 返回对象字面量需要用括号包裹
const createPerson = (name, age) => ({ name, age });
console.log(createPerson('Tom', 25)); // { name: 'Tom', age: 25 }
箭头函数与 this:
// 传统函数的 this 指向调用者
function Timer() {
this.seconds = 0;
setInterval(function() {
this.seconds++; // this 指向 window/global,而非 Timer 实例
console.log(this.seconds); // NaN
}, 1000);
}
// 箭头函数继承外层的 this
function Timer2() {
this.seconds = 0;
setInterval(() => {
this.seconds++; // this 指向 Timer2 实例
console.log(this.seconds); // 1, 2, 3...
}, 1000);
}
// 实际应用
const person = {
name: 'Tom',
hobbies: ['coding', 'reading'],
// ❌ 使用箭头函数作为对象方法(this 不指向 person)
printName: () => {
console.log(this.name); // undefined
},
// ✅ 使用传统函数
printHobbies: function() {
this.hobbies.forEach(hobby => {
// 箭头函数继承 printHobbies 的 this
console.log(`${this.name} likes ${hobby}`);
});
}
};
⚠️ 箭头函数使用注意
不适用场景:
- ❌ 不能作为构造函数使用(不能用
new
调用) - ❌ 没有
arguments
对象(使用剩余参数代替) - ❌ 不能用作对象方法(this 绑定问题)
- ❌ 不能用作事件处理器中需要
this
指向 DOM 元素的场景
适用场景:
- ✅ 适合用作回调函数(如数组方法的回调)
- ✅ 适合用在不需要 this 的场景
- ✅ 适合在 React 组件中作为事件处理器
💡 箭头函数的 this 特性
箭头函数不绑定自己的 this
,而是继承外层作用域的 this
。这使得它非常适合作为回调函数,但不适合作为对象方法或构造函数。在 React、Vue 等框架中,箭头函数被广泛用于事件处理器。
⚡ 扩展运算符(Spread Operator)
// 数组扩展
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2];
console.log(combined); // [1, 2, 3, 4, 5, 6]
// 数组复制(浅拷贝)
const original = [1, 2, 3];
const copy = [...original];
copy.push(4);
console.log(original); // [1, 2, 3]
console.log(copy); // [1, 2, 3, 4]
// 对象扩展(ES2018+)
const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3, d: 4 };
const merged = { ...obj1, ...obj2 };
console.log(merged); // { a: 1, b: 2, c: 3, d: 4 }
// 对象属性覆盖
const defaults = { theme: 'light', lang: 'en' };
const userSettings = { theme: 'dark' };
const finalSettings = { ...defaults, ...userSettings };
console.log(finalSettings); // { theme: 'dark', lang: 'en' }
// 函数参数展开
function sum(a, b, c) {
return a + b + c;
}
const numbers = [1, 2, 3];
console.log(sum(...numbers)); // 6
// 实际应用:合并配置对象
const defaultConfig = {
theme: 'light',
lang: 'zh-CN',
pageSize: 10,
showHeader: true
};
const userConfig = {
theme: 'dark',
pageSize: 20
};
const finalConfig = { ...defaultConfig, ...userConfig };
console.log(finalConfig);
// { theme: 'dark', lang: 'zh-CN', pageSize: 20, showHeader: true }
// 数组去重
const arr = [1, 2, 2, 3, 3, 4];
const uniqueArr = [...new Set(arr)];
console.log(uniqueArr); // [1, 2, 3, 4]
// 数组的最大值/最小值
const nums = [3, 1, 4, 1, 5, 9];
console.log(Math.max(...nums)); // 9
console.log(Math.min(...nums)); // 1
// 字符串转数组
const str = 'hello';
const chars = [...str];
console.log(chars); // ['h', 'e', 'l', 'l', 'o']
// React 中的应用:更新状态
const [items, setItems] = useState([1, 2, 3]);
setItems([...items, 4]); // 添加新项
setItems([0, ...items]); // 在开头添加
🔄 Promise - 异步编程
Promise 是现代 JavaScript 异步编程的基础,代表一个异步操作的最终完成或失败。
Promise 基础
// 创建 Promise
const promise = new Promise((resolve, reject) => {
// 执行异步操作
const success = true;
if (success) {
resolve('操作成功'); // 成功时调用
} else {
reject('操作失败'); // 失败时调用
}
});
// 使用 Promise
promise
.then(result => {
console.log(result); // 操作成功
})
.catch(error => {
console.error(error);
});
Fetch API 示例
fetch
返回一个 Promise 对象,用于发起网络请求:
// 基本用法
const fetchPromise = fetch(
"https://api.example.com/data.json"
);
console.log(fetchPromise); // Promise { <pending> }
fetchPromise.then((response) => {
console.log(`已收到响应:${response.status}`);
});
console.log("已发送请求……");
// 链式调用处理响应数据
fetch("https://api.example.com/products.json")
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
return response.json(); // 返回新的 Promise
})
.then(data => {
console.log(data[0].name);
})
.catch(error => {
console.error('请求失败:', error);
});
Promise 的三种状态
📌 Promise 状态说明
Promise 有三种互斥的状态:
- pending(待定):初始状态,操作进行中
- fulfilled(已兑现):操作成功完成,调用
resolve()
- rejected(已拒绝):操作失败,调用
reject()
Promise 状态特点
Promise 状态一旦改变就不会再变,只能从 pending 转换为 fulfilled 或 rejected。这种不可逆的特性确保了异步操作结果的稳定性。
// 状态转换示例
const promise1 = new Promise((resolve) => {
setTimeout(() => resolve('成功'), 1000);
});
// pending -> fulfilled
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => reject('失败'), 1000);
});
// pending -> rejected
Promise 实用方法
// Promise.all - 所有 Promise 都成功才成功
const promise1 = Promise.resolve(3);
const promise2 = new Promise(resolve => setTimeout(() => resolve('foo'), 100));
const promise3 = Promise.resolve(42);
Promise.all([promise1, promise2, promise3])
.then(values => {
console.log(values); // [3, 'foo', 42]
});
// Promise.race - 返回最先完成的 Promise 结果
Promise.race([
new Promise(resolve => setTimeout(() => resolve('慢'), 500)),
new Promise(resolve => setTimeout(() => resolve('快'), 100))
])
.then(value => {
console.log(value); // '快'
});
// Promise.allSettled - 等待所有 Promise 完成(无论成功或失败)
Promise.allSettled([
Promise.resolve('成功'),
Promise.reject('失败'),
Promise.resolve('成功2')
])
.then(results => {
console.log(results);
// [
// { status: 'fulfilled', value: '成功' },
// { status: 'rejected', reason: '失败' },
// { status: 'fulfilled', value: '成功2' }
// ]
});
// Promise.any - 任意一个成功即返回
Promise.any([
Promise.reject('错误1'),
Promise.resolve('成功'),
Promise.resolve('成功2')
])
.then(value => {
console.log(value); // '成功'
});
封装 Promise 请求
// 封装 GET 请求
function get(url, data) {
return new Promise((resolve, reject) => {
$.ajax({
url: url,
type: "GET",
data: data,
success(result) {
resolve(result);
},
error(error) {
reject(error);
}
});
});
}
// 使用
get('/api/users', { page: 1 })
.then(data => console.log(data))
.catch(error => console.error(error));
⏱️ Async/Await - 异步语法糖
async/await
让异步代码看起来像同步代码,极大提升了代码的可读性。
基本语法
// async 函数声明
async function myFunction() {
// 异步函数体
return '返回值'; // 自动包装成 Promise
}
// 等价于
function myFunction2() {
return Promise.resolve('返回值');
}
// async 箭头函数
const asyncArrow = async () => {
return '结果';
};
await 关键字
// 使用 await 等待 Promise 完成
async function fetchProducts() {
try {
// 等待 fetch 完成
const response = await fetch(
"https://api.example.com/products.json"
);
if (!response.ok) {
throw new Error(`HTTP 请求错误:${response.status}`);
}
// 等待 JSON 解析完成
const json = await response.json();
console.log(json[0].name);
return json;
} catch (error) {
console.error(`无法获取产品列表:${error}`);
throw error; // 重新抛出错误
}
}
fetchProducts();
并行执行多个异步操作
// ❌ 串行执行(耗时较长)
async function sequential() {
const result1 = await fetch('/api/data1');
const result2 = await fetch('/api/data2');
const result3 = await fetch('/api/data3');
return [result1, result2, result3];
}
// ✅ 并行执行(性能更好)
async function parallel() {
const [result1, result2, result3] = await Promise.all([
fetch('/api/data1'),
fetch('/api/data2'),
fetch('/api/data3')
]);
return [result1, result2, result3];
}
错误处理
// 方式1:try-catch
async function fetchData() {
try {
const response = await fetch('/api/data');
const data = await response.json();
return data;
} catch (error) {
console.error('请求失败:', error);
return null;
}
}
// 方式2:catch 方法
async function fetchData2() {
const response = await fetch('/api/data').catch(err => {
console.error('请求失败:', err);
return null;
});
if (!response) return null;
const data = await response.json();
return data;
}
// 方式3:组合使用
async function fetchData3() {
const data = await fetch('/api/data')
.then(res => res.json())
.catch(err => {
console.error(err);
return null;
});
return data;
}
实际应用示例
// 顺序执行异步任务
async function processSteps() {
console.log('步骤 1:开始');
await delay(1000);
console.log('步骤 2:处理中');
await delay(1000);
console.log('步骤 3:完成');
}
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// 循环中使用 await
async function processItems(items) {
for (const item of items) {
await processItem(item); // 逐个处理
}
}
// 并行处理数组
async function processItemsParallel(items) {
await Promise.all(items.map(item => processItem(item)));
}
// 实际应用:批量上传文件
async function uploadFiles(files) {
const uploadPromises = files.map(file => {
return fetch('/api/upload', {
method: 'POST',
body: file
});
});
const results = await Promise.all(uploadPromises);
console.log('所有文件上传完成', results);
}
// 实际应用:带重试的请求
async function fetchWithRetry(url, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(url);
if (response.ok) return await response.json();
} catch (error) {
if (i === maxRetries - 1) throw error;
console.log(`重试 ${i + 1}/${maxRetries}`);
await delay(1000 * (i + 1)); // 递增延迟
}
}
}
// 实际应用:顺序执行异步任务队列
async function runTaskQueue(tasks) {
const results = [];
for (const task of tasks) {
const result = await task();
results.push(result);
}
return results;
}
📦 模块化(Modules)
ES6 模块系统允许将代码拆分成可复用的独立文件。
模块化的优势
- 代码组织:将相关功能组织到独立文件
- 作用域隔离:避免全局命名冲突
- 依赖管理:明确的依赖关系
- 按需加载:提升性能
项目结构示例
project/
├── index.html
├── main.js
└── libs/
├── user.js
└── utils.js
导出(Export)
user.js (src/frontend/javascript/ES6-Common-Knowledge-Points.md:373-402)
// 命名导出
export const user = {
username: "张三",
age: 18
};
export const isAdult = (age) => {
if (age >= 18) {
console.log("成年人");
return true;
} else {
console.log("未成年");
return false;
}
};
// 也可以集中导出
const adminUser = { username: "管理员", role: "admin" };
const checkPermission = (user) => user.role === "admin";
export { adminUser, checkPermission };
// 默认导出(每个模块只能有一个)
export default class User {
constructor(name) {
this.name = name;
}
}
导入(Import)
main.js (src/frontend/javascript/ES6-Common-Knowledge-Points.md:406-416)
// 导入命名导出
import { user, isAdult } from './libs/user.js';
alert("当前用户:" + user.username);
isAdult(user.age);
// 导入默认导出
import User from './libs/user.js';
const newUser = new User('李四');
// 导入全部并重命名
import * as UserModule from './libs/user.js';
console.log(UserModule.user);
UserModule.isAdult(20);
// 重命名导入
import { user as currentUser, isAdult as checkAge } from './libs/user.js';
// 同时导入默认和命名导出
import User, { user, isAdult } from './libs/user.js';
// 仅执行模块(不导入任何内容)
import './libs/init.js';
HTML 中使用模块
index.html (src/frontend/javascript/ES6-Common-Knowledge-Points.md:354-368)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>ES6 模块化示例</title>
<!-- type="module" 声明这是一个 ES6 模块 -->
<script src="main.js" type="module"></script>
</head>
<body>
<h1>模块化测试</h1>
</body>
</html>
动态导入
// 按需加载模块(返回 Promise)
async function loadModule() {
const module = await import('./libs/user.js');
console.log(module.user);
module.isAdult(20);
}
// 条件导入
if (condition) {
import('./module-a.js').then(module => {
module.doSomething();
});
} else {
import('./module-b.js').then(module => {
module.doSomething();
});
}
// 动态路径
const lang = 'zh-CN';
import(`./i18n/${lang}.js`).then(module => {
console.log(module.translations);
});
🛠️ 其他实用特性
BigInt - 大整数类型
ES2020 引入的 BigInt
用于表示任意精度的整数,解决了 JavaScript 中整数精度限制的问题。
// JavaScript 中普通数字的精度限制
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991
console.log(9007199254740992 === 9007199254740993); // true(精度丢失)
// 使用 BigInt(添加 n 后缀)
const bigNum1 = 9007199254740992n;
const bigNum2 = 9007199254740993n;
console.log(bigNum1 === bigNum2); // false(精确表示)
// 使用 BigInt 构造函数
const bigNum3 = BigInt("9007199254740992");
console.log(bigNum3); // 9007199254740992n
// BigInt 运算
const a = 100n;
const b = 200n;
console.log(a + b); // 300n
console.log(a * b); // 20000n
console.log(b / a); // 2n(向下取整)
// 注意事项
// ❌ 不能与普通数字混合运算
// console.log(100n + 200); // TypeError
// ✅ 需要显式转换
console.log(100n + BigInt(200)); // 300n
console.log(Number(100n) + 200); // 300
// 应用场景:处理大数运算、加密算法、精确的财务计算
const hugeNumber = 123456789012345678901234567890n;
console.log(hugeNumber * 2n); // 246913578024691357802469135780n
⚠️ BigInt 注意事项
BigInt
不能与普通数字直接进行运算,需要显式转换JSON.stringify()
不支持 BigInt,需要自定义序列化- 大部分 Math 对象的方法不支持 BigInt
- BigInt 在除法运算中会向下取整(去掉小数部分)
globalThis - 统一的全局对象
ES2020 引入 globalThis
提供了一个标准的方式来访问全局对象,无论在什么环境下。
// 传统方式(不同环境需要不同的方式)
// 浏览器: window
// Node.js: global
// Web Workers: self
// ✅ 统一的方式(兼容所有环境)
console.log(globalThis); // 始终指向全局对象
// 实际应用:编写跨平台代码
function getGlobalObject() {
if (typeof globalThis !== 'undefined') return globalThis;
if (typeof window !== 'undefined') return window;
if (typeof global !== 'undefined') return global;
if (typeof self !== 'undefined') return self;
throw new Error('无法找到全局对象');
}
// 现在可以简化为
const globalObj = globalThis;
模板字符串
// 多行字符串
const message = `
这是一段
多行文本
内容
`;
// 字符串插值
const name = 'Tom';
const age = 25;
const greeting = `Hello, my name is ${name} and I'm ${age} years old.`;
// 表达式计算
const price = 100;
const quantity = 3;
console.log(`总价:${price * quantity} 元`); // 总价:300 元
// 标签模板(高级用法)
function highlight(strings, ...values) {
return strings.reduce((result, str, i) => {
return `${result}${str}<strong>${values[i] || ''}</strong>`;
}, '');
}
const user = 'Tom';
const html = highlight`用户 ${user} 已登录`;
// 用户 <strong>Tom</strong> 已登录
Symbol
// 创建唯一标识符
const id1 = Symbol('id');
const id2 = Symbol('id');
console.log(id1 === id2); // false(每个 Symbol 都是唯一的)
// 作为对象属性(避免命名冲突)
const obj = {
[id1]: 'value1',
[id2]: 'value2'
};
// Symbol 属性不会被常规方法遍历
console.log(Object.keys(obj)); // []
console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(id), Symbol(id)]
Map - 键值对集合
Map
是 ES6 新增的数据结构,相比普通对象,它有以下优势:
- 键可以是任意类型(对象、函数、基本类型)
- 键值对有序(按插入顺序)
- 可以直接获取大小(size 属性)
- 性能更好(频繁增删场景)
// 基本用法
const map = new Map();
map.set('name', 'Tom');
map.set(123, 'number key');
map.set({ id: 1 }, 'object key');
map.set(true, 'boolean key');
console.log(map.get('name')); // Tom
console.log(map.has('name')); // true
console.log(map.size); // 4
// 链式调用
const chainMap = new Map()
.set('a', 1)
.set('b', 2)
.set('c', 3);
// 初始化时传入数组
const userMap = new Map([
['name', 'Tom'],
['age', 25],
['email', '[email protected]']
]);
// 遍历 Map
// 方法1: for...of
for (const [key, value] of userMap) {
console.log(`${key}: ${value}`);
}
// 方法2: forEach
userMap.forEach((value, key) => {
console.log(`${key}: ${value}`);
});
// 获取所有键和值
console.log([...userMap.keys()]); // ['name', 'age', 'email']
console.log([...userMap.values()]); // ['Tom', 25, '[email protected]']
console.log([...userMap.entries()]); // [['name', 'Tom'], ['age', 25], ...]
// 删除和清空
map.delete('name');
map.clear();
// 实际应用:缓存函数结果
const cache = new Map();
function fibonacci(n) {
if (n <= 1) return n;
if (cache.has(n)) {
return cache.get(n);
}
const result = fibonacci(n - 1) + fibonacci(n - 2);
cache.set(n, result);
return result;
}
console.log(fibonacci(10)); // 55
console.log(cache); // 缓存了中间结果
// 实际应用:统计字符出现次数
function countChars(str) {
const map = new Map();
for (const char of str) {
map.set(char, (map.get(char) || 0) + 1);
}
return map;
}
console.log(countChars('hello'));
// Map { 'h' => 1, 'e' => 1, 'l' => 2, 'o' => 1 }
💡 Map vs Object
使用 Map 的场景:
- 键需要是非字符串类型
- 需要频繁增删键值对
- 需要保持插入顺序
- 需要快速获取键值对数量
使用 Object 的场景:
- 键都是字符串或 Symbol
- 需要 JSON 序列化
- 有固定的数据结构
Set - 唯一值集合
Set
是值的集合,每个值只能出现一次。
// 基本用法
const set = new Set([1, 2, 3, 3, 4, 4, 5]);
console.log(set); // Set { 1, 2, 3, 4, 5 }(自动去重)
// 添加和删除
set.add(6);
set.add(6); // 重复添加无效
set.delete(1);
console.log(set.has(2)); // true
console.log(set.size); // 5
// 链式调用
const chainSet = new Set()
.add(1)
.add(2)
.add(3);
// 遍历 Set
for (const value of set) {
console.log(value);
}
set.forEach(value => {
console.log(value);
});
// 数组去重(最常用场景)
const arr = [1, 2, 2, 3, 3, 4, 5, 5];
const uniqueArr = [...new Set(arr)];
console.log(uniqueArr); // [1, 2, 3, 4, 5]
// 字符串去重
const str = 'hello';
const uniqueChars = [...new Set(str)].join('');
console.log(uniqueChars); // 'helo'
// Set 运算
const setA = new Set([1, 2, 3, 4]);
const setB = new Set([3, 4, 5, 6]);
// 并集
const union = new Set([...setA, ...setB]);
console.log(union); // Set { 1, 2, 3, 4, 5, 6 }
// 交集
const intersection = new Set([...setA].filter(x => setB.has(x)));
console.log(intersection); // Set { 3, 4 }
// 差集
const difference = new Set([...setA].filter(x => !setB.has(x)));
console.log(difference); // Set { 1, 2 }
// 实际应用:去除数组中的重复对象(基于属性)
const users = [
{ id: 1, name: 'Tom' },
{ id: 2, name: 'Jerry' },
{ id: 1, name: 'Tom' }, // 重复
];
const uniqueUsers = Array.from(
new Map(users.map(user => [user.id, user])).values()
);
console.log(uniqueUsers);
// [{ id: 1, name: 'Tom' }, { id: 2, name: 'Jerry' }]
// 实际应用:判断数组是否包含重复值
function hasDuplicates(arr) {
return new Set(arr).size !== arr.length;
}
console.log(hasDuplicates([1, 2, 3])); // false
console.log(hasDuplicates([1, 2, 2, 3])); // true
💡 Set 实用技巧
- 数组去重:
[...new Set(arr)]
- 判断是否有重复:
new Set(arr).size !== arr.length
- 集合运算:利用扩展运算符和数组方法
- 性能:
Set.has()
比Array.includes()
快得多(O(1) vs O(n))
Class 类
// 类定义
class Person {
// 构造函数
constructor(name, age) {
this.name = name;
this.age = age;
}
// 实例方法
sayHello() {
console.log(`Hello, I'm ${this.name}`);
}
// 静态方法
static create(name, age) {
return new Person(name, age);
}
// Getter
get info() {
return `${this.name}, ${this.age} years old`;
}
// Setter
set nickName(value) {
this._nickName = value;
}
}
// 继承
class Student extends Person {
constructor(name, age, grade) {
super(name, age); // 调用父类构造函数
this.grade = grade;
}
// 方法重写
sayHello() {
super.sayHello(); // 调用父类方法
console.log(`I'm in grade ${this.grade}`);
}
}
// 使用
const student = new Student('Tom', 18, 12);
student.sayHello();
// Hello, I'm Tom
// I'm in grade 12
🔧 实用工具函数
递归查找树形结构数据
/**
* 在树形结构中查找目标节点及其完整路径
* @param {Array} options - 树形数据
* @param {string} targetValue - 目标值
* @returns {Array|null} - 节点路径数组或 null
*/
const findNodePath = (options, targetValue) => {
const path = []; // 存储当前路径
// 递归遍历函数
function traverse(nodes) {
for (let node of nodes) {
path.push(node); // 当前节点加入路径
// 找到目标节点
if (node.label === targetValue) {
return [...path]; // 返回路径副本
}
// 递归遍历子节点
if (node.children && node.children.length > 0) {
const result = traverse(node.children);
if (result) return result;
}
// 回溯:移除当前节点
path.pop();
}
return null;
}
return traverse(options);
};
// 使用示例
const categoryTree = [
{
label: '电子产品',
children: [
{
label: '手机',
children: [
{ label: 'iPhone' },
{ label: 'Android' }
]
},
{ label: '电脑' }
]
},
{
label: '图书',
children: [
{ label: '技术' },
{ label: '小说' }
]
}
];
const targetValue = 'iPhone';
const path = findNodePath(categoryTree, targetValue);
if (path) {
console.log('路径:', path.map(node => node.label).join(' > '));
// 路径: 电子产品 > 手机 > iPhone
} else {
console.log('未找到节点');
}
数组扁平化
// 方法1:使用 flat()
const nested = [1, [2, [3, [4, 5]]]];
console.log(nested.flat(Infinity)); // [1, 2, 3, 4, 5]
// 方法2:递归实现
function flattenArray(arr) {
return arr.reduce((result, item) => {
return result.concat(Array.isArray(item) ? flattenArray(item) : item);
}, []);
}
console.log(flattenArray(nested)); // [1, 2, 3, 4, 5]
对象深拷贝
// 方法1:JSON 序列化(简单但有局限性)
const obj1 = { a: 1, b: { c: 2 } };
const copy1 = JSON.parse(JSON.stringify(obj1));
// 方法2:递归实现(更完善)
function deepClone(obj, hash = new WeakMap()) {
if (obj === null) return null;
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
if (typeof obj !== 'object') return obj;
// 处理循环引用
if (hash.has(obj)) return hash.get(obj);
const cloneObj = new obj.constructor();
hash.set(obj, cloneObj);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloneObj[key] = deepClone(obj[key], hash);
}
}
return cloneObj;
}
// 方法3:使用 structuredClone()(现代浏览器)
const copy3 = structuredClone(obj1);
防抖和节流
// 防抖:延迟执行,频繁触发只执行最后一次
function debounce(func, delay) {
let timer = null;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
// 使用场景:搜索框输入
const searchInput = document.querySelector('#search');
searchInput.addEventListener('input', debounce((e) => {
console.log('搜索:', e.target.value);
}, 500));
// 节流:限制执行频率,一定时间内只执行一次
function throttle(func, delay) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= delay) {
lastTime = now;
func.apply(this, args);
}
};
}
// 使用场景:滚动事件
window.addEventListener('scroll', throttle(() => {
console.log('滚动位置:', window.scrollY);
}, 200));
⚡ 性能优化建议
1. 解构赋值的性能
// ✅ 推荐:适度使用解构
const { name, age } = user;
// ⚠️ 注意:深层解构可能影响可读性和性能
// 过深的解构不如分步访问
const { a: { b: { c: { d } } } } = obj; // 不推荐
const d = obj.a.b.c.d; // 更清晰
2. 扩展运算符的性能
// ⚠️ 大数组使用扩展运算符要注意性能
const arr1 = new Array(100000).fill(1);
const arr2 = [...arr1]; // 对于大数组,考虑使用 slice()
// ✅ 更高效的方式
const arr3 = arr1.slice(); // 性能更好
3. Promise 和 async/await 的选择
// ✅ 独立的异步操作,使用 Promise.all 并行执行
async function fetchAllData() {
const [users, posts, comments] = await Promise.all([
fetchUsers(),
fetchPosts(),
fetchComments()
]);
return { users, posts, comments };
}
// ⚠️ 避免不必要的串行等待
// 错误示例:
async function badExample() {
const users = await fetchUsers(); // 等待 1s
const posts = await fetchPosts(); // 等待 1s
const comments = await fetchComments(); // 等待 1s
// 总共 3s
}
// 正确示例:
async function goodExample() {
const [users, posts, comments] = await Promise.all([
fetchUsers(),
fetchPosts(),
fetchComments()
]);
// 只需 1s(并行执行)
}
4. 模块化的性能优化
// ✅ 使用命名导出,便于 Tree Shaking
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
// ✅ 动态导入实现代码分割
async function loadFeature() {
const module = await import('./heavy-feature.js');
module.initialize();
}
// ✅ 在合适的时机加载模块
button.addEventListener('click', async () => {
const { initChart } = await import('./chart.js');
initChart();
});
💡 性能优化建议
- 使用
const
而非let
可以帮助 JavaScript 引擎进行优化 - 扩展运算符在小数组上性能很好,大数组考虑传统方法
- 利用 Promise.all 并行执行独立的异步任务
- 使用动态 import 实现代码分割,提升首屏加载速度
- 箭头函数比普通函数略快(无需绑定 this、arguments)
✅ 最佳实践总结
变量声明
- ✅ 优先使用
const
- ✅ 需要重新赋值时使用
let
- ❌ 避免使用
var
函数
- ✅ 使用箭头函数作为回调
- ✅ 使用默认参数替代
||
操作符 - ✅ 使用剩余参数替代
arguments
- ❌ 不要用箭头函数作为对象方法
异步
- ✅ 优先使用
async/await
- ✅ 使用
Promise.all()
并行执行独立任务 - ✅ 始终处理错误(try-catch 或 .catch)
- ❌ 避免回调地狱
模块化
- ✅ 每个文件只做一件事
- ✅ 使用命名导出便于重构
- ✅ 默认导出用于主要功能
- ✅ 使用动态导入优化性能
代码风格
- ✅ 使用模板字符串替代字符串拼接
- ✅ 使用解构赋值简化代码
- ✅ 使用扩展运算符进行数组/对象操作
- ✅ 使用可选链避免深层判断
❓ 常见问题 FAQ
Q1: let、const 和 var 应该如何选择?
A: 遵循以下优先级:
- 默认使用
const
(约 80% 的场景) - 需要重新赋值时使用
let
(约 20% 的场景) - 永远不使用
var
(存在作用域和变量提升问题)
// ✅ 推荐
const API_URL = 'https://api.example.com'; // 常量
const user = { name: 'Tom' }; // 引用不变的对象
let count = 0; // 需要重新赋值的变量
// ❌ 避免
var oldStyle = 'deprecated'; // 不要使用 var
Q2: 箭头函数什么时候不能用?
A: 以下场景避免使用箭头函数:
- 需要动态
this
的对象方法 - 需要使用
arguments
对象 - 需要作为构造函数(
new
) - 需要使用
prototype
// ❌ 不要用箭头函数作为对象方法
const person = {
name: 'Tom',
sayHi: () => {
console.log(this.name); // this 不指向 person
}
};
// ✅ 使用普通函数
const person2 = {
name: 'Tom',
sayHi() {
console.log(this.name); // 正确输出 Tom
}
};
Q3: Promise 和 async/await 有什么区别?
A: async/await
是 Promise 的语法糖,让异步代码更像同步代码:
// Promise 风格
function fetchUser() {
return fetch('/api/user')
.then(res => res.json())
.then(data => {
console.log(data);
return data;
})
.catch(err => console.error(err));
}
// async/await 风格(更清晰)
async function fetchUser() {
try {
const res = await fetch('/api/user');
const data = await res.json();
console.log(data);
return data;
} catch (err) {
console.error(err);
}
}
选择建议:
- 简单的异步操作:两者都可以
- 复杂的异步流程:优先使用
async/await
(更易读) - 需要并行执行:使用
Promise.all()
Q4: 解构赋值会影响性能吗?
A: 正常使用不会有明显性能问题,但要注意:
// ✅ 简单解构,性能影响微乎其微
const { name, age } = user;
// ⚠️ 过度嵌套的解构会影响可读性
const { a: { b: { c: { d: { e } } } } } = obj; // 不推荐
// ✅ 分步访问更清晰
const value = obj.a.b.c.d.e;
Q5: 扩展运算符(...)和 Object.assign() 有什么区别?
A: 两者都可以复制对象,但有细微差别:
const obj = { a: 1, b: 2 };
// 扩展运算符(ES2018,更现代)
const copy1 = { ...obj };
// Object.assign()(ES6)
const copy2 = Object.assign({}, obj);
// 主要区别:
// 1. 语法:扩展运算符更简洁
// 2. 性能:现代引擎中差异很小
// 3. 兼容性:扩展运算符需要更新的环境
推荐: 优先使用扩展运算符(更简洁、更现代)
Q6: 如何判断是否应该使用模块化?
A: 以下情况建议使用模块化:
- ✅ 项目超过 500 行代码
- ✅ 有多个开发者协作
- ✅ 代码需要复用
- ✅ 需要明确的依赖管理
- ✅ 使用构建工具(Webpack、Vite 等)
// ✅ 模块化的好处
// utils.js
export const formatDate = (date) => { /* ... */ };
export const validateEmail = (email) => { /* ... */ };
// main.js
import { formatDate, validateEmail } from './utils.js';
// 清晰的依赖关系,便于维护和测试
Q7: Map/Set 和 Object/Array 如何选择?
A: 根据使用场景选择:
需求 | 推荐使用 | 原因 |
---|---|---|
键值对存储(键为字符串) | Object | 简单、直观 |
键值对存储(键为任意类型) | Map | 支持任意类型的键 |
频繁增删键值对 | Map | 性能更好 |
唯一值集合 | Set | 自动去重 |
有序列表 | Array | 保持顺序 |
需要数组方法(map、filter) | Array | 丰富的数组方法 |
// ✅ 使用 Map 存储非字符串键
const userScores = new Map();
userScores.set({ id: 1 }, 100); // 对象作为键
// ✅ 使用 Set 去重
const uniqueIds = new Set([1, 2, 2, 3, 3, 4]);
console.log([...uniqueIds]); // [1, 2, 3, 4]
📚 参考资源
- MDN - JavaScript
- ES6 入门教程 - 阮一峰
- ECMAScript 规范
- Can I Use - 浏览器兼容性
- JavaScript.info - 现代 JavaScript 教程
- V8 博客 - JavaScript 性能优化
💡 总结
ES6+ 为 JavaScript 带来了革命性的改进,掌握这些特性能够:
- 📝 编写更简洁、可读的代码
- 🚀 提升开发效率和代码质量
- 🛡️ 减少常见错误和 bug
- 🎯 更好地组织和维护项目
希望本文能帮助你全面掌握 ES6+ 的核心知识点!