ES6 实用技巧总结
ES6(ECMAScript 2015)是 JavaScript 语言的一次重大更新,引入了许多新特性,极大地提升了开发效率和代码可读性。本文将深入探讨 ES6 的实用技巧,结合理论说明和实际案例,帮助开发者更好地掌握这些特性。
一、解构赋值:优雅的数据提取
解构赋值允许从数组或对象中提取值,并赋给变量,使代码更简洁易读。
1.1 对象解构赋值
对象解构可以轻松提取对象属性,支持重命名和默认值。
// 基本对象解构
const user = {
name: "John",
age: 30,
email: "john@example.com"
};
const { name, age } = user;
console.log(name); // "John"
console.log(age); // 30
// 重命名变量
const { name: userName, age: userAge } = user;
console.log(userName); // "John"
console.log(userAge); // 30
// 默认值
const { name, age, gender = 'unknown' } = user;
console.log(gender); // 'unknown'
注意事项:解构的对象不能为 undefined
或 null
,否则会抛出错误。
1.2 数组解构赋值
数组解构按位置提取值,支持跳过元素和默认值。
// 基本数组解构
const numbers = [1, 2, 3, 4];
const [first, second] = numbers;
console.log(first); // 1
console.log(second); // 2
// 跳过元素
const [first, , third] = numbers;
console.log(first); // 1
console.log(third); // 3
// 默认值
const [a = 10, b = 20] = [1];
console.log(a); // 1
console.log(b); // 20
// 交换变量
let x = 1, y = 2;
[x, y] = [y, x];
console.log(x); // 2
console.log(y); // 1
1.3 函数参数解构
解构可用于函数参数,直接提取所需值。
// 对象参数解构
function getUserInfo({name, age, email = 'N/A'}) {
console.log(`Name: ${name}, Age: ${age}, Email: ${email}`);
}
getUserInfo(user); // Name: John, Age: 30, Email: john@example.com
// 数组参数解构
function getFirstTwo([first, second]) {
console.log(`First: ${first}, Second: ${second}`);
}
getFirstTwo(numbers); // First: 1, Second: 2
二、扩展运算符与剩余参数:灵活的数据处理
扩展运算符(...
)和剩余参数简化了数组和对象的操作。
2.1 扩展运算符的应用
扩展运算符可将数组或对象展开。
// 合并数组
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combinedArr = [...arr1, ...arr2];
console.log(combinedArr); // [1, 2, 3, 4, 5, 6]
// 合并对象
const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3, d: 4 };
const combinedObj = { ...obj1, ...obj2 };
console.log(combinedObj); // { a: 1, b: 2, c: 3, d: 4 }
// 对象属性覆盖
const defaultSettings = { theme: 'light', fontSize: 16 };
const userSettings = { fontSize: 18, showNotifications: true };
const finalSettings = { ...defaultSettings, ...userSettings };
console.log(finalSettings); // { theme: 'light', fontSize: 18, showNotifications: true }
// 复制数组或对象(浅拷贝)
const originalArray = [1, 2, 3];
const copiedArray = [...originalArray];
const originalObject = { a: 1, b: 2 };
const copiedObject = { ...originalObject };
2.2 剩余参数的妙用
剩余参数将多个参数收集到数组中。
// 函数中的剩余参数
function sum(a, b, ...otherNumbers) {
let total = a + b;
for (const num of otherNumbers) {
total += num;
}
return total;
}
console.log(sum(1, 2, 3, 4, 5)); // 15
// 解构中的剩余参数
const [first, second, ...rest] = [1, 2, 3, 4, 5];
console.log(first); // 1
console.log(second); // 2
console.log(rest); // [3, 4, 5]
const { name, ...details } = user;
console.log(name); // "John"
console.log(details); // { age: 30, email: "john@example.com" }
三、模板字符串:强大的字符串处理
模板字符串使用反引号(`
)定义,支持多行字符串和表达式嵌入。
3.1 基本用法
const name = "Alice";
const age = 25;
const message = `${name} is ${age} years old.`;
console.log(message); // "Alice is 25 years old."
// 多行字符串
const multiLine = `
Name: ${name}
Age: ${age}
Email: alice@example.com
`;
console.log(multiLine);
3.2 标签模板
标签模板可以自定义模板字符串的处理方式。
function highlight(strings, ...values) {
return strings.reduce((result, str, i) => {
return `${result}${str}<span class="highlight">${values[i] || ''}</span>`;
}, '');
}
const name = "Alice";
const age = 25;
const highlighted = highlight`Name: ${name}, Age: ${age}`;
console.log(highlighted); // "Name: <span class="highlight">Alice</span>, Age: <span class="highlight">25</span>"
四、箭头函数:简洁的函数表达式
箭头函数提供更简洁的函数语法,并继承外部 this
。
4.1 语法与特性
// 传统函数
const add = function(a, b) {
return a + b;
};
// 箭头函数
const add = (a, b) => a + b;
// 单个参数可省略括号
const square = x => x * x;
// 无参数需要空括号
const getTime = () => new Date();
// 多行函数体需大括号和return
const multiply = (a, b) => {
const result = a * b;
return result;
};
4.2 this 绑定行为
箭头函数没有自己的 this
,继承自外部作用域。
// 传统函数中的this问题
function Counter() {
this.count = 0;
setInterval(function() {
this.count++; // 这里的this指向全局对象或undefined
console.log(this.count);
}, 1000);
}
// 解决方案1: 使用箭头函数
function Counter() {
this.count = 0;
setInterval(() => {
this.count++; // 这里的this继承自Counter函数
console.log(this.count);
}, 1000);
}
// 解决方案2: 使用bind
function Counter() {
this.count = 0;
setInterval(function() {
this.count++;
console.log(this.count);
}.bind(this), 1000);
}
五、增强的对象字面量:更简洁的对象操作
ES6 提供了更简洁的对象字面量语法。
5.1 属性与方法简写
const name = "Bob";
const age = 35;
// 传统方式
const user1 = {
name: name,
age: age,
speak: function() {
console.log(`Hi, I'm ${this.name}`);
}
};
// ES6简写
const user2 = {
name, // 属性简写
age, // 属性简写
speak() { // 方法简写
console.log(`Hi, I'm ${this.name}`);
}
};
5.2 计算属性名
const propPrefix = "user_";
const id = 123;
const user = {
[`${propPrefix}${id}`]: "John Doe",
{
return this[`${propPrefix}${id}`];
}
};
console.log(user.user_123); // "John Doe"
console.log(user.getuser_Info()); // "John Doe"
六、参数处理技巧:默认值与强制参数
6.1 参数默认值
// ES5方式
function multiply(a, b) {
b = typeof b !== 'undefined' ? b : 1;
return a * b;
}
// ES6默认参数
function multiply(a, b = 1) {
return a * b;
}
console.log(multiply(5)); // 5
console.log(multiply(5, 2)); // 10
// 默认参数表达式
function getDefaultValue() {
return 10;
}
function calculate(a, b = getDefaultValue()) {
return a + b;
}
console.log(calculate(5)); // 15
6.2 强制参数
通过默认参数实现强制要求参数。
const required = () => {
throw new Error('Missing parameter');
};
const add = (a = required(), b = required()) => a + b;
add(1, 2); // 3
add(1); // Error: Missing parameter
七、数组增强方法:更高效的数据处理
ES6 引入了许多有用的数组方法。
7.1 find() 与 findIndex()
const users = [
{ id: 1, name: 'John', active: true },
{ id: 2, name: 'Jane', active: false },
{ id: 3, name: 'Bob', active: true }
];
// find返回第一个匹配的元素
const activeUser = users.find(user => user.active);
console.log(activeUser); // { id: 1, name: 'John', active: true }
// findIndex返回第一个匹配元素的索引
const inactiveIndex = users.findIndex(user => !user.active);
console.log(inactiveIndex); // 1
7.2 includes()
const numbers = [1, 2, 3, 4, 5];
// 检查数组是否包含某元素
console.log(numbers.includes(3)); // true
console.log(numbers.includes(6)); // false
// 与indexOf的比较
console.log(numbers.indexOf(3) !== -1); // true,但includes更直观
7.3 flat() 与 flatMap()
// 扁平化数组
const nestedArray = [1, [2, [3, [4]]]];
const flattened = nestedArray.flat(Infinity);
console.log(flattened); // [1, 2, 3, 4]
// flatMap先映射后扁平化
const sentences = ["Hello world", "Goodbye world"];
const words = sentences.flatMap(sentence => sentence.split(' '));
console.log(words); // ["Hello", "world", "Goodbye", "world"]
八、Promise 与异步编程:更优雅的异步处理
Promise 提供了更优雅的异步编程方式。
8.1 基本用法
// 创建Promise
const fetchData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
const data = { id: 1, name: 'John' };
// 模拟成功
resolve(data);
// 模拟失败
// reject(new Error('Data not found'));
}, 1000);
});
};
// 使用Promise
fetchData()
.then(data => {
console.log('Data received:', data);
return processData(data);
})
.then(processedData => {
console.log('Data processed:', processedData);
})
.catch(error => {
console.error('Error:', error.message);
})
.finally(() => {
console.log('Operation completed');
});
8.2 Promise 工具方法
// Promise.all等待所有Promise完成
const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
const promise3 = new Promise((resolve) => {
setTimeout(() => resolve(3), 1000);
});
Promise.all([promise1, promise2, promise3])
.then(values => {
console.log(values); // [1, 2, 3] (在所有Promise完成后)
})
.catch(error => {
console.error('One of the promises failed');
});
// Promise.race返回最先完成的Promise结果
Promise.race([promise1, promise2, promise3])
.then(value => {
console.log(value); // 1 (最先完成的Promise结果)
});
// Promise.allSettled等待所有Promise完成(无论成功失败)
Promise.allSettled([promise1, Promise.reject('error'), promise3])
.then(results => {
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log('Value:', result.value);
} else {
console.log('Reason:', result.reason);
}
});
});
九、async/await:同步风格的异步代码
async/await 让异步代码看起来像同步代码。
9.1 基本用法
// async函数总是返回Promise
async function getUserData(userId) {
try {
const user = await fetchUser(userId); // 等待Promise解决
const posts = await fetchUserPosts(user.id);
const comments = await fetchUserComments(user.id);
return {
user,
posts,
comments
};
} catch (error) {
console.error('Failed to get user data:', error);
throw error; // 重新抛出错误
}
}
// 使用async函数
getUserData(123)
.then(data => {
console.log('User data:', data);
})
.catch(error => {
console.error('Error:', error);
});
// 立即执行async函数
(async () => {
try {
const data = await getUserData(123);
console.log('User data:', data);
} catch (error) {
console.error('Error:', error);
}
})();
9.2 错误处理模式
// 方式1: try/catch
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
return data;
} catch (error) {
console.error('Fetch failed:', error);
// 返回默认值或重新抛出错误
return { default: 'data' };
}
}
// 方式2: 在调用处处理
async function fetchData() {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
return data;
}
// 调用时处理错误
fetchData()
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
// 方式3: 使用辅助函数
function withTimeout(promise, timeout) {
return Promise.race([
promise,
new Promise((_, reject) => {
setTimeout(() => reject(new Error('Timeout')), timeout);
})
]);
}
async function fetchWithTimeout() {
try {
const data = await withTimeout(fetchData(), 5000);
console.log('Data received:', data);
} catch (error) {
console.error('Error:', error.message);
}
}
十、模块系统:更好的代码组织
ES6 模块提供了官方的模块系统。
10.1 导出模块
// math.js
// 命名导出
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
export function multiply(a, b) {
return a * b;
}
// 默认导出
export default class Calculator {
constructor() {
this.name = 'Calculator';
}
subtract(a, b) {
return a - b;
}
}
// 导出列表
const version = '1.0';
const author = 'John Doe';
export { version, author as creator };
10.2 导入模块
// 主文件.js
// 默认导入
import Calculator from './math.js';
// 命名导入
import { PI, add, multiply } from './math.js';
// 重命名导入
import { version as ver, creator } from './math.js';
// 全部导入
import * as math from './math.js';
// 使用导入的内容
const calc = new Calculator();
console.log(calc.subtract(5, 3)); // 2
console.log(PI); // 3.14159
console.log(add(2, 3)); // 5
console.log(ver); // '1.0'
console.log(creator); // 'John Doe'
console.log(math.multiply(4, 5)); // 20
10.3 动态导入
// 按需加载模块
async function loadModule(moduleName) {
try {
const module = await import(`./modules/${moduleName}.js`);
module.initialize();
return module;
} catch (error) {
console.error(`Failed to load module ${moduleName}:`, error);
return null;
}
}
// 使用
button.addEventListener('click', async () => {
const analytics = await loadModule('analytics');
if (analytics) {
analytics.trackEvent('button-click');
}
});
十一、常用工具技巧:提升开发效率
11.1 可选链操作符(Optional Chaining)
// 传统方式
const city = user && user.address && user.address.city;
// 可选链方式
const city = user?.address?.city;
// 函数调用可选链
const result = obj.someMethod?.();
// 数组项可选链
const firstItem = arr?.[0];
// 结合空值合并
const city = user?.address?.city ?? 'Unknown';
11.2 空值合并运算符(Nullish Coalescing)
// 传统方式
const value = input !== null && input !== undefined ? input : 'default';
// 空值合并方式
const value = input ?? 'default';
// 与逻辑或的区别
console.log(0 || 'default'); // 'default' (0是falsy)
console.log(0 ?? 'default'); // 0 (0不是null或undefined)
console.log('' || 'default'); // 'default' (''是falsy)
console.log('' ?? 'default'); // '' (''不是null或undefined)
console.log(null || 'default'); // 'default'
console.log(null ?? 'default'); // 'default'
console.log(undefined || 'default'); // 'default'
console.log(undefined ?? 'default'); // 'default'
11.3 使用 Set 进行数组去重
const numbers = [1, 2, 2, 3, 4, 4, 5];
const uniqueNumbers = [...new Set(numbers)];
console.log(uniqueNumbers); // [1, 2, 3, 4, 5]
// 对象数组去重(基于特定属性)
const users = [
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' },
{ id: 1, name: 'John' }
];
const uniqueUsers = [...new Map(users.map(user => [user.id, user])).values()];
console.log(uniqueUsers); // [{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }]
11.4 使用 Map 作为键值存储
// 创建Map
const map = new Map();
// 设置值
map.set('name', 'John');
map.set(1, 'number one');
map.set({ key: 'obj' }, 'object value');
// 获取值
console.log(map.get('name')); // 'John'
console.log(map.get(1)); // 'number one'
// 检查键是否存在
console.log(map.has('name')); // true
// 获取大小
console.log(map.size); // 3
// 删除键
map.delete('name');
// 清空Map
map.clear();
// 遍历Map
map.forEach((value, key) => {
console.log(key, value);
});
// 转换为数组
const entriesArray = Array.from(map.entries());
const keysArray = Array.from(map.keys());
const valuesArray = Array.from(map.values());
十二、实用代码片段:日常开发中的利器
12.1 数组操作
// 数组分块
function chunkArray(array, size) {
const result = [];
for (let i = 0; i < array.length; i += size) {
result.push(array.slice(i, i + size));
}
return result;
}
console.log(chunkArray([1, 2, 3, 4, 5], 2)); // [[1, 2], [3, 4], [5]]
// 数组随机排序
function shuffleArray(array) {
return array.sort(() => Math.random() - 0.5);
}
console.log(shuffleArray([1, 2, 3, 4, 5]));
// 数组交集
function arrayIntersection(arr1, arr2) {
return arr1.filter(item => arr2.includes(item));
}
console.log(arrayIntersection([1, 2, 3], [2, 3, 4])); // [2, 3]
// 数组差集
function arrayDifference(arr1, arr2) {
return arr1.filter(item => !arr2.includes(item));
}
console.log(arrayDifference([1, 2, 3], [2, 3, 4])); // [1]
12.2 对象操作
// 对象深拷贝
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') return obj;
if (obj instanceof Date) return new Date(obj);
if (obj instanceof Array) return obj.map(item => deepClone(item));
const cloned = {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = deepClone(obj[key]);
}
}
return cloned;
}
// 对象属性过滤
function filterObject(obj, predicate) {
return Object.fromEntries(
Object.entries(obj).filter(([key, value]) => predicate(key, value))
);
}
const user = { name: 'John', age: 30, password: 'secret' };
const filtered = filterObject(user, (key) => key !== 'password');
console.log(filtered); // { name: 'John', age: 30 }
// 对象合并(深合并)
function deepMerge(target, source) {
for (const key in source) {
if (source.hasOwnProperty(key)) {
if (typeof source[key] === 'object' && source[key] !== null) {
if (!target[key]) target[key] = Array.isArray(source[key]) ? [] : {};
deepMerge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
}
return target;
}
12.3 函数工具
// 函数防抖
function debounce(func, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(this, args), delay);
};
}
// 函数节流
function throttle(func, delay) {
let lastCall = 0;
return function(...args) {
const now = new Date().getTime();
if (now - lastCall < delay) return;
lastCall = now;
return func.apply(this, args);
};
}
// 函数柯里化
function curry(func) {
return function curried(...args) {
if (args.length >= func.length) {
return func.apply(this, args);
} else {
return function(...args2) {
return curried.apply(this, args.concat(args2));
};
}
};
}
// 使用柯里化
const add = (a, b, c) => a + b + c;
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6
console.log(curriedAdd(1, 2, 3)); // 6
十三、ES6+ 新特性:超越 ES6 的实用功能
13.1 String.prototype 方法
// padStart 和 padEnd
console.log('5'.padStart(2, '0')); // '05'
console.log('5'.padEnd(2, '0')); // '50'
console.log('12'.padStart(5, '0')); // '00012'
// trimStart 和 trimEnd
const text = ' Hello World ';
console.log(text.trimStart()); // 'Hello World '
console.log(text.trimEnd()); // ' Hello World'
console.log(text.trim()); // 'Hello World'
// startsWith 和 endsWith
const filename = 'document.txt';
console.log(filename.startsWith('doc')); // true
console.log(filename.endsWith('.txt')); // true
13.2 Object 静态方法
const person = {
name: 'John',
age: 30,
occupation: 'Developer'
};
// Object.values
console.log(Object.values(person)); // ['John', 30, 'Developer']
// Object.entries
console.log(Object.entries(person));
// [['name', 'John'], ['age', 30], ['occupation', 'Developer']]
// Object.fromEntries
const entries = [['name', 'John'], ['age', 30]];
console.log(Object.fromEntries(entries)); // { name: 'John', age: 30 }
// Object.hasOwn
console.log(Object.hasOwn(person, 'name')); // true
console.log(Object.hasOwn(person, 'toString')); // false
13.3 数字分隔符
// 提高大数字的可读性
const billion = 1_000_000_000;
console.log(billion); // 1000000000
const binary = 0b1010_0001_1000_0101;
console.log(binary); // 41349
const hex = 0xFF_FF_FF;
console.log(hex); // 16777215
十四、最佳实践与性能考虑
14.1 解构赋值的性能考虑
虽然解构赋值使代码更简洁,但在性能关键代码中需谨慎使用:
// 多次解构 vs 一次性解构
// 不推荐:多次解构
function processUser(user) {
const { name } = user;
// 一些操作
const { age } = user;
// 更多操作
const { email } = user;
}
// 推荐:一次性解构
function processUser(user) {
const { name, age, email } = user;
// 使用解构后的变量
}
// 深层解构的性能影响
// 避免在循环中进行深层解构
for (const item of items) {
// 不推荐:在循环中深层解构
const { metadata: { createdBy, createdAt } } = item;
// 推荐:在循环外解构或使用浅层解构
const { metadata } = item;
const createdBy = metadata.createdBy;
const createdAt = metadata.createdAt;
}
14.2 Promise 使用建议
// 避免Promise嵌套
// 不推荐
getUser()
.then(user => {
getPosts(user.id)
.then(posts => {
getComments(posts[0].id)
.then(comments => {
console.log(comments);
});
});
});
// 推荐:使用Promise链
getUser()
.then(user => getPosts(user.id))
.then(posts => getComments(posts[0].id))
.then(comments => console.log(comments))
.catch(error => console.error(error));
// 使用async/await进一步简化
async function fetchUserData() {
try {
const user = await getUser();
const posts = await getPosts(user.id);
const comments = await getComments(posts[0].id);
console.log(comments);
} catch (error) {
console.error(error);
}
}
14.3 模块组织建议
// 按功能组织模块
// utils/math.js - 数学相关工具函数
export function add(a, b) { return a + b; }
export function multiply(a, b) { return a * b; }
// utils/string.js - 字符串相关工具函数
export function capitalize(str) { return str.charAt(0).toUpperCase() + str.slice(1); }
export function truncate(str, length) { return str.length > length ? str.slice(0, length) + '...' : str; }
// utils/index.js - 统一导出
export * from './math';
export * from './string';
export { default as Logger } from './logger';
// 按层级组织模块
// components/Button.js - UI组件
export default function Button({ text, onClick }) {
return <button onClick={onClick}>{text}</button>;
}
// hooks/useFetch.js - 自定义Hook
export default function useFetch(url) {
// Hook实现
}
// services/api.js - API调用
export async function fetchUser(id) {
// API调用实现
}
十五、浏览器兼容性与转译
15.1 兼容性考虑
虽然现代浏览器大多支持 ES6+ 特性,但仍需考虑兼容性:
// 特性检测使用
if (typeof Symbol !== 'undefined') {
// 使用Symbol
} else {
// 回退方案
}
// 使用Babel转译
// .babelrc配置
{
"presets": [
["@babel/preset-env", {
"targets": {
"browsers": ["> 1%", "last 2 versions"]
},
"useBuiltIns": "usage",
"corejs": 3
}]
]
}
// 使用Polyfill
import 'core-js/stable';
import 'regenerator-runtime/runtime';
// 按需引入Polyfill
import 'core-js/features/array/flat';
import 'core-js/features/array/flat-map';
import 'core-js/features/promise';
15.2 现代模式与传统模式
<!-- 现代浏览器使用module,传统浏览器使用nomodule -->
<script type="module" src="modern.js"></script>
<script nomodule src="legacy.js"></script>
<!-- package.json 浏览器列表配置 -->
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
总结
ES6 及其后续版本为 JavaScript 带来了革命性的变化,极大地提升了开发效率和代码质量。通过合理运用解构赋值、扩展运算符、箭头函数、Promise 等特性,可以编写出更简洁、可读性更高、更易维护的代码。
关键要点回顾
- 解构赋值提供了从数组和对象中提取数据的简洁语法,支持默认值和重命名。
- 扩展运算符简化了数组和对象的操作,使合并、复制等操作更加直观。
- 模板字符串支持多行字符串和表达式嵌入,大大改善了字符串处理。
- 箭头函数提供了更简洁的函数语法和自动的
this
绑定。 - Promise 和 async/await彻底改变了异步编程模式,使异步代码更易读写。
- 模块系统提供了官方的模块化解决方案,改善了代码组织方式。
- 增强的对象和数组方法提供了更多处理数据的工具。
- 新特性如可选链和空值合并进一步简化了代码,减少了模板代码。
实践建议
- 渐进式采用:不必一次性重写所有代码,可以逐步引入 ES6+ 特性。
- 考虑兼容性:根据目标用户群体选择合适的转译和 polyfill 策略。
- 保持一致性:在团队中制定并遵循统一的代码风格和特性使用规范。
- 性能意识:在性能关键代码中谨慎使用某些特性,如深层解构。