引言
ES6(ECMAScript 2015)是JavaScript历史上最重要的更新之一,它引入了大量新特性,极大地提升了语言的表达力和开发效率。然而,随着新特性的引入,开发者也面临着配置不当、兼容性问题和潜在陷阱。本文将全面解析ES6的配置亮点,提供详细的代码示例,帮助你避免常见陷阱,并显著提升代码质量。
1. Babel配置:现代JavaScript的基石
1.1 为什么需要Babel?
ES6引入了许多新语法,但并非所有浏览器都完全支持。Babel是一个JavaScript编译器,能将ES6+代码转换为向后兼容的ES5代码,确保代码在各种环境中运行一致。
1.2 基础配置示例
首先,安装必要的包:
npm install --save-dev @babel/core @babel/cli @babel/preset-env
创建 .babelrc 配置文件:
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"browsers": ["last 2 versions", "ie >= 11"]
},
"useBuiltIns": "usage",
"corejs": 3
}
]
],
"plugins": []
}
配置详解:
targets:指定目标环境,这里配置为最近2个版本的浏览器和IE11+useBuiltIns: "usage":根据代码实际使用情况按需引入polyfillcorejs: 3:使用core-js 3版本提供完整的ES6+ polyfill
1.3 常见陷阱与解决方案
陷阱1:polyfill重复引入
// 错误做法:在多个文件中手动引入
import '@babel/polyfill';
import 'core-js/stable';
import 'regenerator-runtime/runtime';
// 正确做法:让Babel自动处理
// 在babel配置中设置 useBuiltIns: "usage"
陷阱2:忽略浏览器列表配置
// 在package.json中配置browserslist,让多个工具共享配置
"browserslist": [
"> 1%",
"last 2 versions",
"not dead",
"not ie <= 10"
]
2. 模块系统配置与最佳实践
2.1 ES6模块 vs CommonJS
ES6模块是静态的,这带来了更好的优化和tree-shaking支持。
// ES6模块 - 静态导入/导出
// math.js
export const add = (a, b) => a + b;
export const multiply = (a, b) => a * b;
// app.js
import { add, multiply } from './math.js';
// CommonJS - 动态导入/导出
// math.js
module.exports = {
add: (a, b) => a + b,
multiply: (a, b) => a * b
};
// app.js
const math = require('./math.js');
2.2 动态导入与代码分割
动态import()是ES2020特性,但常与ES6配合使用,实现按需加载:
// 配置Webpack支持动态导入
// webpack.config.js
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].chunk.js',
path: path.resolve(__dirname, 'dist')
},
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
}
}
}
}
};
// 使用动态导入实现代码分割
async function loadComponent() {
const { default: Component } = await import('./HeavyComponent.js');
return Component;
}
// 在React中的应用
const HeavyComponent = lazy(() => import('./HeavyComponent.js'));
2.3 避免循环依赖
循环依赖是模块系统的常见陷阱:
// a.js
import { b } from './b.js';
export const a = () => {
console.log('a called');
b();
};
// b.js
import { a } from './a.js';
export const b = () => {
console.log('b called');
// 注意:此时a可能还未完全初始化
// a(); // 可能导致错误
};
// 解决方案:重构代码结构,或使用依赖注入
// 使用中间模块解耦
// utils.js
export const callB = () => {
const { b } = require('./b.js');
b();
};
// a.js
import { callB } from './utils.js';
export const a = () => {
console.log('a called');
callB();
};
3. 解构赋值与默认值配置
3.1 对象解构
// 基础用法
const user = { name: 'Alice', age: 30, city: 'NYC' };
const { name, age } = user;
// 重命名
const { name: userName, age: userAge } = user;
// 默认值
const { name = 'Guest', country = 'USA' } = user;
// 嵌套解构
const data = {
user: { id: 1, profile: { email: 'test@example.com' } }
};
const { user: { profile: { email } } } = data;
3.2 数组解构
const [first, second, ...rest] = [1, 2, 3, 4, 5];
// first=1, second=2, rest=[3,4,5]
// 交换变量
let a = 1, b = 2;
[a, b] = [b, a]; // a=2, b=1
// 函数参数解构
function processUser({ name, age = 18 }) {
console.log(`${name} is ${age} years old`);
}
processUser({ name: 'Bob' }); // Bob is 18 years old
3.3 避免解构陷阱
陷阱:解构null/undefined
// 错误:解构null会抛出错误
const { name } = null; // TypeError
// 正确做法:提供默认值或检查
const { name } = null || {};
const safeDestructure = (obj) => {
if (!obj) return {};
const { name, age } = obj;
return { name, age };
};
4. 箭头函数与this绑定
4.1 箭头函数特性
// 词法this:继承外层作用域的this值
const obj = {
name: 'MyObject',
regularFunc: function() {
console.log(this.name); // 'MyObject'
},
arrowFunc: () => {
// this指向定义时的上下文(通常是window/global)
console.log(this.name); // undefined(在严格模式下)
}
};
// 箭头函数没有自己的this、arguments、super或new.target
const createCounter = () => {
let count = 0;
return {
increment: () => count++,
getValue: () => count
};
};
4.2 事件处理器中的陷阱
// 陷阱:在类方法中使用箭头函数作为回调
class Button {
constructor() {
this.count = 0;
// 正确:箭头函数绑定外层this
this.handleClick = () => {
this.count++;
console.log(`Clicked ${this.count} times`);
};
}
// 错误:普通函数作为回调会丢失this
handleClickWrong() {
this.count++;
}
}
// 在React中的应用
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
// 推荐:在构造函数中绑定或使用箭头函数属性
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({ count: prevState.count + 1 }));
}
render() {
return <button onClick={this.handleClick}>Click me</button>;
}
}
5. Promise与异步编程配置
5.1 Promise基础与配置
// 创建Promise
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
Math.random() > 0.5 ? resolve('Success') : reject('Error');
}, 1000);
});
// 链式调用
myPromise
.then(result => {
console.log(result);
return result.toUpperCase();
})
.then(upperResult => {
console.log(upperResult);
})
.catch(error => {
console.error('Error:', error);
})
.finally(() => {
console.log('Cleanup');
});
5.2 Promise.all与错误处理
// Promise.all:并行执行多个Promise
const fetchUser = () => Promise.resolve({ id: 1, name: 'Alice' });
const fetchPosts = () => Promise.resolve([{ id: 1, title: 'Post 1' }]);
const fetchComments = () => Promise.reject('Network Error');
// 正确处理Promise.all的错误
Promise.all([
fetchUser(),
fetchPosts(),
fetchComments().catch(err => ({ error: err })) // 捕获单个错误
]).then(([user, posts, comments]) => {
console.log('User:', user);
// 处理可能的错误
if (comments.error) {
console.warn('Comments failed:', comments.error);
}
});
// Promise.allSettled:获取所有结果(包括失败)
Promise.allSettled([
fetchUser(),
fetchPosts(),
fetchComments()
]).then(results => {
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log('Success:', result.value);
} else {
console.log('Failed:', result.reason);
}
});
});
5.3 避免Promise陷阱
陷阱1:忘记return
// 错误:链式调用中忘记return
fetch('/api/data')
.then(response => response.json())
.then(data => {
// 这里没有return,下一个then接收到的是undefined
process(data);
})
.then(result => {
console.log(result); // undefined
});
// 正确:
fetch('/api/data')
.then(response => response.json())
=> {
process(data);
return data; // 必须return
})
.then(result => {
console.log(result); // 正确的数据
});
陷阱2:Promise构造反模式
// 错误:不必要的Promise包装
function getData() {
return new Promise((resolve, reject) => {
fetch('/api/data')
.then(response => response.json())
.then(resolve)
.catch(reject);
});
}
// 正确:直接返回Promise
function getData() {
return fetch('/api/data').then(response => response.json());
}
6. async/await配置与错误处理
6.1 async/await基础
// async函数总是返回Promise
async function fetchUserData(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
return data;
} catch (1error) {
console.error('Fetch failed:', error);
throw error; // 重新抛出以便调用者处理
}
}
// 使用
fetchUserData(1)
.then(user => console.log(user))
.catch(err => console.error(err));
6.2 并行与串行执行
// 串行执行:依赖前一个结果
async function sequential() {
const user = await fetchUser();
const posts = await fetchPosts(user.id); // 依赖user
return { user, posts };
}
// 并行执行:无依赖关系
async function parallel() {
const [user, config] = await Promise.all([
fetchUser(),
fetchConfig()
]);
return { user, config };
}
// 部分并行
async function mixed() {
const userPromise = fetchUser();
const configPromise = fetchConfig();
const user = await userPromise;
const config = await configPromise;
const posts = await fetchPosts(user.id);
return { user, config, posts };
}
6.3 高级错误处理模式
// 重试机制
async function fetchWithRetry(url, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(url);
return await response.json();
} catch (error) {
if (i === maxRetries - 1) throw error;
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
}
}
}
// 超时控制
async function fetchWithTimeout(url, timeout = 5000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, { signal: controller.signal });
clearTimeout(timeoutId);
return await response.json();
} catch (error) {
if (error.name === 'AbortError') {
throw new Error('Request timed out');
}
throw error;
}
}
7. 类与继承配置
7.1 ES6类基础
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a sound`);
}
// 静态方法
static create(name) {
return new Animal(name);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // 必须调用super
this.breed = breed;
}
speak() {
console.log(`${this.name} barks`);
}
// Getter
get description() {
return `${this.name} is a ${this.breed}`;
}
}
// 使用
const dog = new Dog('Rex', 'German Shepherd');
dog.speak(); // Rex barks
console.log(dog.description); // Rex is a German Shepherd
7.2 私有字段(ES2022)
class Counter {
#count = 0; // 私有字段
increment() {
this.#count++;
}
getValue() {
return this.#count;
}
}
const counter = new Counter();
counter.increment();
console.log(counter.getValue()); // 1
console.log(counter.#count); // SyntaxError: Private field '#count' must be declared in an enclosing class
7.3 避免类的陷阱
陷阱:this丢失
class Timer {
constructor() {
this.seconds = 0;
// 陷阱:setInterval的回调会丢失this
setInterval(this.tick, 1000); // this.tick中的this是undefined
}
tick() {
this.seconds++; // TypeError: Cannot read property 'seconds' of undefined
}
}
// 解决方案1:箭头函数绑定
class TimerFixed1 {
constructor() {
this.seconds = 0;
setInterval(() => this.tick(), 1000);
}
tick() { this.seconds++; }
}
// 解决方案2:bind
class TimerFixed2 {
constructor() {
this.seconds = 0;
this.tick = this.tick.bind(this);
setInterval(this.tick, 1000);
}
tick() { this秒数++; }
}
8. 模板字符串与标签模板
8.1 基础用法
// 多行字符串
const multiline = `This is a
multiline
string`;
// 插值
const name = 'Alice';
const age = 30;
const message = `Hello, ${name}. You are ${age} years old.`;
// 嵌入表达式
const cost = 100;
const total = `Total: $${cost * 1.1}`; // "Total: $110"
8.2 标签模板
// 创建标签函数
function highlight(strings, ...values) {
return strings.reduce((result, str, i) => {
const value = values[i] || '';
return result + str + `<mark>${value}</mark>`;
}, '');
}
// 使用
const user = { name: 'Alice', role: 'Admin' };
const html = highlight`User: ${user.name}, Role: ${user.role}`;
// Result: "User: <mark>Alice</mark>, Role: <mark>Admin</mark>"
// 安全HTML转义
function safeHTML(strings, ...values) {
const escape = (str) => String(str).replace(/[&<>"']/g, s => ({
'&': '&', '<': '<', '>': '>', '"': '"', "'": '''
}[s]));
return strings.reduce((result, str, i) => {
return result + str + escape(values[i] || '');
}, '');
}
9. Map/Set与数据结构优化
9.1 Map vs Object
// Map的优势:键可以是任意类型
const map = new Map();
map.set('string', 'value');
map.set(123, 'number key');
map.set({}, 'object key');
map.set(() => {}, 'function key');
// Map保持插入顺序
const orderedMap = new Map([
['a', 1],
['b', 2],
['c', 3]
]);
console.log([...orderedMap]); // [['a',1], ['b',2], ['c',3]]
// Map有更好的性能(频繁增删场景)
// Object需要O(n)查找,Map是O(1)
9.2 Set与数组去重
// 数组去重
const numbers = [1, 2, 2, 3, 4, 4, 5];
const unique = [...new Set(numbers)]; // [1,2,3,4,5]
// Set运算
const setA = new Set([1, 2, 3]);
const setB = new Set([2, 3, 4]);
// 并集
const union = new Set([...setA, ...setB]); // {1,2,3,4}
// 交集
const intersection = new Set([...setA].filter(x => setB.has(x))); // {2,3}
// 差集
const difference = new Set([...setA].filter(x => !setB.has(x))); // {1}
10. 配置工具链与构建优化
10.1 Webpack配置示例
// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
mode: 'production',
entry: {
main: './src/index.js',
vendor: ['react', 'react-dom']
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].chunk.js',
publicPath: '/'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
cacheDirectory: true // 启用缓存加速构建
}
}
}
]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
collapseWhitespace: true,
removeComments: true
}
})
],
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
reuseExistingChunk: true
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
},
runtimeChunk: 'single'
},
// 性能优化
performance: {
maxAssetSize: 512000,
maxEntrypointSize: 512000,
hints: 'warning'
}
};
10.2 ESLint配置
// .eslintrc.json
{
"env": {
"browser": true,
"es2021": true,
"node": true
},
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:import/recommended",
"plugin:prettier/recommended"
],
"parserOptions": {
"ecmaVersion": 2021,
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
}
},
"plugins": ["react", "import", "prettier"],
"rules": {
"prettier/prettier": "error",
"no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
"no-console": "warn",
"prefer-const": "error",
"no-var": "error",
"react/prop-types": "off", // 使用TypeScript时关闭
"import/no-unresolved": "error",
"import/no-anonymous-default-export": "warn"
},
"settings": {
"react": {
"version": "detect"
}
}
}
10.3 Prettier配置
// .prettierrc
{
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": true,
"trailingComma": "es5",
"bracketSpacing": true,
"arrowParens": "always",
"endOfLine": "lf"
}
11. 性能优化与最佳实践
11.1 避免不必要的对象创建
// 不好的做法:每次调用都创建新对象
function processUser(user) {
return {
...user,
processed: true
};
}
// 更好的做法:如果不需要新对象,直接修改
function processUserInPlace(user) {
user.processed = true;
return user;
}
// 或者使用Object.freeze防止意外修改
const DEFAULT_CONFIG = Object.freeze({
timeout: 5000,
retries: 3
});
11.2 使用Object.freeze优化
// 冻结对象可以提升性能(V8优化)
const CONFIG = Object.freeze({
API_URL: 'https://api.example.com',
TIMEOUT: 5000,
MAX_RETRIES: 3
});
// 冻结嵌套对象
function deepFreeze(obj) {
Object.freeze(obj);
Object.getOwnPropertyNames(obj).forEach(prop => {
if (obj[prop] && typeof obj[prop] === 'object' && !Object.isFrozen(obj[prop])) {
deepFreeze(obj[prop]);
}
});
return obj;
}
11.3 使用WeakMap/WeakSet避免内存泄漏
// 传统Map会导致内存泄漏
const cache = new Map();
function process(obj) {
if (cache.has(obj)) return cache.get(obj);
const result = heavyComputation(obj);
cache.set(obj, obj);
return result;
}
// 使用WeakMap:当obj被垃圾回收时,条目自动删除
const weakCache = new WeakMap();
function processWeak(obj) {
if (weakCache.has(obj)) return weakCache.get(obj);
const result = heavyComputation(obj);
weakCache.set(obj, result);
return result;
}
12. 测试与调试配置
12.1 Jest配置
// jest.config.js
module.exports = {
testEnvironment: 'jsdom',
collectCoverageFrom: [
'src/**/*.{js,jsx}',
'!src/index.js',
'!src/reportWebVitals.js'
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
},
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1'
},
setupFilesAfterEnv: ['<rootDir>/src/setupTests.js'],
transform: {
'^.+\\.[jt]sx?$': 'babel-jest'
},
// 监听配置
watchPlugins: [
'jest-watch-typeahead/filename',
'jest-watch-typeahead/testname'
]
};
12.2 测试示例
// sum.js
export const sum = (a, b) => a + b;
// sum.test.js
import { sum } from './sum';
describe('sum function', () => {
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3);
});
test('handles decimal numbers', () => {
expect(sum(0.1, 0.2)).toBeCloseTo(0.3);
});
});
// 异步测试
async function fetchData() {
const response = await fetch('/api/data');
return response.json();
}
test('fetchData returns expected data', async () => {
// Mock fetch
global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve({ id: 1, name: 'Test' })
})
);
const data = await fetchData();
expect(data).toEqual({ id: 1, name: 'Test' });
});
13. 总结与最佳实践清单
13.1 配置检查清单
✅ Babel配置
- 使用
@babel/preset-env并配置正确的targets - 启用
useBuiltIns: "usage"按需polyfill - 配置browserslist共享配置
✅ 模块系统
- 优先使用ES6模块
- 使用动态import()进行代码分割
- 避免循环依赖
✅ 异步编程
- 使用async/await替代回调地狱
- 使用Promise.allSettled处理并行异步操作
- 实现重试和超时机制
✅ 性能优化
- 使用Map/Set替代普通对象/数组(特定场景)
- 使用Object.freeze优化常量对象
- 使用WeakMap避免内存泄漏
✅ 代码质量
- 配置ESLint和Prettier
- 编写单元测试
- 使用TypeScript增强类型安全
13.2 常见陷阱总结
- polyfill管理:避免手动引入,让Babel自动处理
- this绑定:在类方法中使用箭头函数或bind
- Promise链:记得return值
- 解构null:提供默认值或检查
- 循环依赖:重构代码结构
- 内存泄漏:使用WeakMap/WeakSet存储对象引用
- 性能:避免不必要的对象创建,使用Object.freeze
通过遵循这些配置和最佳实践,你可以充分利用ES6的特性,编写出更高效、更可靠、更易维护的JavaScript代码。# ES6配置亮点全解析:如何避免常见陷阱并提升代码质量
引言
ES6(ECMAScript 2015)是JavaScript历史上最重要的更新之一,它引入了大量新特性,极大地提升了语言的表达力和开发效率。然而,随着新特性的引入,开发者也面临着配置不当、兼容性问题和潜在陷阱。本文将全面解析ES6的配置亮点,提供详细的代码示例,帮助你避免常见陷阱,并显著提升代码质量。
1. Babel配置:现代JavaScript的基石
1.1 为什么需要Babel?
ES6引入了许多新语法,但并非所有浏览器都完全支持。Babel是一个JavaScript编译器,能将ES6+代码转换为向后兼容的ES5代码,确保代码在各种环境中运行一致。
1.2 基础配置示例
首先,安装必要的包:
npm install --save-dev @babel/core @babel/cli @babel/preset-env
创建 .babelrc 配置文件:
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"browsers": ["last 2 versions", "ie >= 11"]
},
"useBuiltIns": "usage",
"corejs": 3
}
]
],
"plugins": []
}
配置详解:
targets:指定目标环境,这里配置为最近2个版本的浏览器和IE11+useBuiltIns: "usage":根据代码实际使用情况按需引入polyfillcorejs: 3:使用core-js 3版本提供完整的ES6+ polyfill
1.3 常见陷阱与解决方案
陷阱1:polyfill重复引入
// 错误做法:在多个文件中手动引入
import '@babel/polyfill';
import 'core-js/stable';
import 'regenerator-runtime/runtime';
// 正确做法:让Babel自动处理
// 在babel配置中设置 useBuiltIns: "usage"
陷阱2:忽略浏览器列表配置
// 在package.json中配置browserslist,让多个工具共享配置
"browserslist": [
"> 1%",
"last 2 versions",
"not dead",
"not ie <= 10"
]
2. 模块系统配置与最佳实践
2.1 ES6模块 vs CommonJS
ES6模块是静态的,这带来了更好的优化和tree-shaking支持。
// ES6模块 - 静态导入/导出
// math.js
export const add = (a, b) => a + b;
export const multiply = (a, b) => a * b;
// app.js
import { add, multiply } from './math.js';
// CommonJS - 动态导入/导出
// math.js
module.exports = {
add: (a, b) => a + b,
multiply: (a, b) => a * b
};
// app.js
const math = require('./math.js');
2.2 动态导入与代码分割
动态import()是ES2020特性,但常与ES6配合使用,实现按需加载:
// 配置Webpack支持动态导入
// webpack.config.js
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].chunk.js',
path: path.resolve(__dirname, 'dist')
},
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
}
}
}
}
};
// 使用动态导入实现代码分割
async function loadComponent() {
const { default: Component } = await import('./HeavyComponent.js');
return Component;
}
// 在React中的应用
const HeavyComponent = lazy(() => import('./HeavyComponent.js'));
2.3 避免循环依赖
循环依赖是模块系统的常见陷阱:
// a.js
import { b } from './b.js';
export const a = () => {
console.log('a called');
b();
};
// b.js
import { a } from './a.js';
export const b = () => {
console.log('b called');
// 注意:此时a可能还未完全初始化
// a(); // 可能导致错误
};
// 解决方案:重构代码结构,或使用依赖注入
// 使用中间模块解耦
// utils.js
export const callB = () => {
const { b } = require('./b.js');
b();
};
// a.js
import { callB } from './utils.js';
export const a = () => {
console.log('a called');
callB();
};
3. 解构赋值与默认值配置
3.1 对象解构
// 基础用法
const user = { name: 'Alice', age: 30, city: 'NYC' };
const { name, age } = user;
// 重命名
const { name: userName, age: userAge } = user;
// 默认值
const { name = 'Guest', country = 'USA' } = user;
// 嵌套解构
const data = {
user: { id: 1, profile: { email: 'test@example.com' } }
};
const { user: { profile: { email } } } = data;
3.2 数组解构
const [first, second, ...rest] = [1, 2, 3, 4, 5];
// first=1, second=2, rest=[3,4,5]
// 交换变量
let a = 1, b = 2;
[a, b] = [b, a]; // a=2, b=1
// 函数参数解构
function processUser({ name, age = 18 }) {
console.log(`${name} is ${age} years old`);
}
processUser({ name: 'Bob' }); // Bob is 18 years old
3.3 避免解构陷阱
陷阱:解构null/undefined
// 错误:解构null会抛出错误
const { name } = null; // TypeError
// 正确做法:提供默认值或检查
const { name } = null || {};
const safeDestructure = (obj) => {
if (!obj) return {};
const { name, age } = obj;
return { name, age };
};
4. 箭头函数与this绑定
4.1 箭头函数特性
// 词法this:继承外层作用域的this值
const obj = {
name: 'MyObject',
regularFunc: function() {
console.log(this.name); // 'MyObject'
},
arrowFunc: () => {
// this指向定义时的上下文(通常是window/global)
console.log(this.name); // undefined(在严格模式下)
}
};
// 箭头函数没有自己的this、arguments、super或new.target
const createCounter = () => {
let count = 0;
return {
increment: () => count++,
getValue: () => count
};
};
4.2 事件处理器中的陷阱
// 陷阱:在类方法中使用箭头函数作为回调
class Button {
constructor() {
this.count = 0;
// 正确:箭头函数绑定外层this
this.handleClick = () => {
this.count++;
console.log(`Clicked ${this.count} times`);
};
}
// 错误:普通函数作为回调会丢失this
handleClickWrong() {
this.count++;
}
}
// 在React中的应用
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
// 推荐:在构造函数中绑定或使用箭头函数属性
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({ count: prevState.count + 1 }));
}
render() {
return <button onClick={this.handleClick}>Click me</button>;
}
}
5. Promise与异步编程配置
5.1 Promise基础与配置
// 创建Promise
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
Math.random() > 0.5 ? resolve('Success') : reject('Error');
}, 1000);
});
// 链式调用
myPromise
.then(result => {
console.log(result);
return result.toUpperCase();
})
.then(upperResult => {
console.log(upperResult);
})
.catch(error => {
console.error('Error:', error);
})
.finally(() => {
console.log('Cleanup');
});
5.2 Promise.all与错误处理
// Promise.all:并行执行多个Promise
const fetchUser = () => Promise.resolve({ id: 1, name: 'Alice' });
const fetchPosts = () => Promise.resolve([{ id: 1, title: 'Post 1' }]);
const fetchComments = () => Promise.reject('Network Error');
// 正确处理Promise.all的错误
Promise.all([
fetchUser(),
fetchPosts(),
fetchComments().catch(err => ({ error: err })) // 捕获单个错误
]).then(([user, posts, comments]) => {
console.log('User:', user);
// 处理可能的错误
if (comments.error) {
console.warn('Comments failed:', comments.error);
}
});
// Promise.allSettled:获取所有结果(包括失败)
Promise.allSettled([
fetchUser(),
fetchPosts(),
fetchComments()
]).then(results => {
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log('Success:', result.value);
} else {
console.log('Failed:', result.reason);
}
});
});
5.3 避免Promise陷阱
陷阱1:忘记return
// 错误:链式调用中忘记return
fetch('/api/data')
.then(response => response.json())
.then(data => {
// 这里没有return,下一个then接收到的是undefined
process(data);
})
.then(result => {
console.log(result); // undefined
});
// 正确:
fetch('/api/data')
.then(response => response.json())
=> {
process(data);
return data; // 必须return
})
.then(result => {
console.log(result); // 正确的数据
});
陷阱2:Promise构造反模式
// 错误:不必要的Promise包装
function getData() {
return new Promise((resolve, reject) => {
fetch('/api/data')
.then(response => response.json())
.then(resolve)
.catch(reject);
});
}
// 正确:直接返回Promise
function getData() {
return fetch('/api/data').then(response => response.json());
}
6. async/await配置与错误处理
6.1 async/await基础
// async函数总是返回Promise
async function fetchUserData(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('Fetch failed:', error);
throw error; // 重新抛出以便调用者处理
}
}
// 使用
fetchUserData(1)
.then(user => console.log(user))
.catch(err => console.error(err));
6.2 并行与串行执行
// 串行执行:依赖前一个结果
async function sequential() {
const user = await fetchUser();
const posts = await fetchPosts(user.id); // 依赖user
return { user, posts };
}
// 并行执行:无依赖关系
async function parallel() {
const [user, config] = await Promise.all([
fetchUser(),
fetchConfig()
]);
return { user, config };
}
// 部分并行
async function mixed() {
const userPromise = fetchUser();
const configPromise = fetchConfig();
const user = await userPromise;
const config = await configPromise;
const posts = await fetchPosts(user.id);
return { user, config, posts };
}
6.3 高级错误处理模式
// 重试机制
async function fetchWithRetry(url, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(url);
return await response.json();
} catch (error) {
if (i === maxRetries - 1) throw error;
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
}
}
}
// 超时控制
async function fetchWithTimeout(url, timeout = 5000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, { signal: controller.signal });
clearTimeout(timeoutId);
return await response.json();
} catch (error) {
if (error.name === 'AbortError') {
throw new Error('Request timed out');
}
throw error;
}
}
7. 类与继承配置
7.1 ES6类基础
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a sound`);
}
// 静态方法
static create(name) {
return new Animal(name);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // 必须调用super
this.breed = breed;
}
speak() {
console.log(`${this.name} barks`);
}
// Getter
get description() {
return `${this.name} is a ${this.breed}`;
}
}
// 使用
const dog = new Dog('Rex', 'German Shepherd');
dog.speak(); // Rex barks
console.log(dog.description); // Rex is a German Shepherd
7.2 私有字段(ES2022)
class Counter {
#count = 0; // 私有字段
increment() {
this.#count++;
}
getValue() {
return this.#count;
}
}
const counter = new Counter();
counter.increment();
console.log(counter.getValue()); // 1
console.log(counter.#count); // SyntaxError: Private field '#count' must be declared in an enclosing class
7.3 避免类的陷阱
陷阱:this丢失
class Timer {
constructor() {
this.seconds = 0;
// 陷阱:setInterval的回调会丢失this
setInterval(this.tick, 1000); // this.tick中的this是undefined
}
tick() {
this.seconds++; // TypeError: Cannot read property 'seconds' of undefined
}
}
// 解决方案1:箭头函数绑定
class TimerFixed1 {
constructor() {
this.seconds = 0;
setInterval(() => this.tick(), 1000);
}
tick() { this.seconds++; }
}
// 解决方案2:bind
class TimerFixed2 {
constructor() {
this.seconds = 0;
this.tick = this.tick.bind(this);
setInterval(this.tick, 1000);
}
tick() { this秒数++; }
}
8. 模板字符串与标签模板
8.1 基础用法
// 多行字符串
const multiline = `This is a
multiline
string`;
// 插值
const name = 'Alice';
const age = 30;
const message = `Hello, ${name}. You are ${age} years old.`;
// 嵌入表达式
const cost = 100;
const total = `Total: $${cost * 1.1}`; // "Total: $110"
8.2 标签模板
// 创建标签函数
function highlight(strings, ...values) {
return strings.reduce((result, str, i) => {
const value = values[i] || '';
return result + str + `<mark>${value}</mark>`;
}, '');
}
// 使用
const user = { name: 'Alice', role: 'Admin' };
const html = highlight`User: ${user.name}, Role: ${user.role}`;
// Result: "User: <mark>Alice</mark>, Role: <mark>Admin</mark>"
// 安全HTML转义
function safeHTML(strings, ...values) {
const escape = (str) => String(str).replace(/[&<>"']/g, s => ({
'&': '&', '<': '<', '>': '>', '"': '"', "'": '''
}[s]));
return strings.reduce((result, str, i) => {
return result + str + escape(values[i] || '');
}, '');
}
9. Map/Set与数据结构优化
9.1 Map vs Object
// Map的优势:键可以是任意类型
const map = new Map();
map.set('string', 'value');
map.set(123, 'number key');
map.set({}, 'object key');
map.set(() => {}, 'function key');
// Map保持插入顺序
const orderedMap = new Map([
['a', 1],
['b', 2],
['c', 3]
]);
console.log([...orderedMap]); // [['a',1], ['b',2], ['c',3]]
// Map有更好的性能(频繁增删场景)
// Object需要O(n)查找,Map是O(1)
9.2 Set与数组去重
// 数组去重
const numbers = [1, 2, 2, 3, 4, 4, 5];
const unique = [...new Set(numbers)]; // [1,2,3,4,5]
// Set运算
const setA = new Set([1, 2, 3]);
const setB = new Set([2, 3, 4]);
// 并集
const union = new Set([...setA, ...setB]); // {1,2,3,4}
// 交集
const intersection = new Set([...setA].filter(x => setB.has(x))); // {2,3}
// 差集
const difference = new Set([...setA].filter(x => !setB.has(x))); // {1}
10. 配置工具链与构建优化
10.1 Webpack配置示例
// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
mode: 'production',
entry: {
main: './src/index.js',
vendor: ['react', 'react-dom']
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].chunk.js',
publicPath: '/'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
cacheDirectory: true // 启用缓存加速构建
}
}
}
]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
collapseWhitespace: true,
removeComments: true
}
})
],
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
reuseExistingChunk: true
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
},
runtimeChunk: 'single'
},
// 性能优化
performance: {
maxAssetSize: 512000,
maxEntrypointSize: 512000,
hints: 'warning'
}
};
10.2 ESLint配置
// .eslintrc.json
{
"env": {
"browser": true,
"es2021": true,
"node": true
},
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:import/recommended",
"plugin:prettier/recommended"
],
"parserOptions": {
"ecmaVersion": 2021,
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
}
},
"plugins": ["react", "import", "prettier"],
"rules": {
"prettier/prettier": "error",
"no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
"no-console": "warn",
"prefer-const": "error",
"no-var": "error",
"react/prop-types": "off", // 使用TypeScript时关闭
"import/no-unresolved": "error",
"import/no-anonymous-default-export": "warn"
},
"settings": {
"react": {
"version": "detect"
}
}
}
10.3 Prettier配置
// .prettierrc
{
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": true,
"trailingComma": "es5",
"bracketSpacing": true,
"arrowParens": "always",
"endOfLine": "lf"
}
11. 性能优化与最佳实践
11.1 避免不必要的对象创建
// 不好的做法:每次调用都创建新对象
function processUser(user) {
return {
...user,
processed: true
};
}
// 更好的做法:如果不需要新对象,直接修改
function processUserInPlace(user) {
user.processed = true;
return user;
}
// 或者使用Object.freeze防止意外修改
const DEFAULT_CONFIG = Object.freeze({
timeout: 5000,
retries: 3
});
11.2 使用Object.freeze优化
// 冻结对象可以提升性能(V8优化)
const CONFIG = Object.freeze({
API_URL: 'https://api.example.com',
TIMEOUT: 5000,
MAX_RETRIES: 3
});
// 冻结嵌套对象
function deepFreeze(obj) {
Object.freeze(obj);
Object.getOwnPropertyNames(obj).forEach(prop => {
if (obj[prop] && typeof obj[prop] === 'object' && !Object.isFrozen(obj[prop])) {
deepFreeze(obj[prop]);
}
});
return obj;
}
11.3 使用WeakMap/WeakSet避免内存泄漏
// 传统Map会导致内存泄漏
const cache = new Map();
function process(obj) {
if (cache.has(obj)) return cache.get(obj);
const result = heavyComputation(obj);
cache.set(obj, obj);
return result;
}
// 使用WeakMap:当obj被垃圾回收时,条目自动删除
const weakCache = new WeakMap();
function processWeak(obj) {
if (weakCache.has(obj)) return weakCache.get(obj);
const result = heavyComputation(obj);
weakCache.set(obj, result);
return result;
}
12. 测试与调试配置
12.1 Jest配置
// jest.config.js
module.exports = {
testEnvironment: 'jsdom',
collectCoverageFrom: [
'src/**/*.{js,jsx}',
'!src/index.js',
'!src/reportWebVitals.js'
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
},
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1'
},
setupFilesAfterEnv: ['<rootDir>/src/setupTests.js'],
transform: {
'^.+\\.[jt]sx?$': 'babel-jest'
},
// 监听配置
watchPlugins: [
'jest-watch-typeahead/filename',
'jest-watch-typeahead/testname'
]
};
12.2 测试示例
// sum.js
export const sum = (a, b) => a + b;
// sum.test.js
import { sum } from './sum';
describe('sum function', () => {
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3);
});
test('handles decimal numbers', () => {
expect(sum(0.1, 0.2)).toBeCloseTo(0.3);
});
});
// 异步测试
async function fetchData() {
const response = await fetch('/api/data');
return response.json();
}
test('fetchData returns expected data', async () => {
// Mock fetch
global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve({ id: 1, name: 'Test' })
})
);
const data = await fetchData();
expect(data).toEqual({ id: 1, name: 'Test' });
});
13. 总结与最佳实践清单
13.1 配置检查清单
✅ Babel配置
- 使用
@babel/preset-env并配置正确的targets - 启用
useBuiltIns: "usage"按需polyfill - 配置browserslist共享配置
✅ 模块系统
- 优先使用ES6模块
- 使用动态import()进行代码分割
- 避免循环依赖
✅ 异步编程
- 使用async/await替代回调地狱
- 使用Promise.allSettled处理并行异步操作
- 实现重试和超时机制
✅ 性能优化
- 使用Map/Set替代普通对象/数组(特定场景)
- 使用Object.freeze优化常量对象
- 使用WeakMap避免内存泄漏
✅ 代码质量
- 配置ESLint和Prettier
- 编写单元测试
- 使用TypeScript增强类型安全
13.2 常见陷阱总结
- polyfill管理:避免手动引入,让Babel自动处理
- this绑定:在类方法中使用箭头函数或bind
- Promise链:记得return值
- 解构null:提供默认值或检查
- 循环依赖:重构代码结构
- 内存泄漏:使用WeakMap/WeakSet存储对象引用
- 性能:避免不必要的对象创建,使用Object.freeze
通过遵循这些配置和最佳实践,你可以充分利用ES6的特性,编写出更高效、更可靠、更易维护的JavaScript代码。
