最近review代码的时候,看到一些还在用var声明变量、用function写满屏回调的代码,我真的有点头疼。
你是不是也遇到过这样的困扰:代码写着写着就乱了,变量莫名其妙被修改,回调嵌套到怀疑人生?其实这些问题,ES6+早就给出了优雅的解决方案。
今天我就带你彻底告别老旧的JS写法,用8个核心特性让你的代码质量直接翻倍!每个特性我都会配上详细注释的代码示例,保证你能立刻上手。
let和const:告别变量提升的噩梦
还记得用var时那些诡异的现象吗?变量莫名其妙被提升,循环计数器失效... let和const就是来拯救你的。
1// 老写法 - 充满陷阱 2var count = 10; 3if (true) { 4 var count = 20; // 啊哦,外面的count也被修改了! 5} 6console.log(count); // 输出20,意外吧? 7 8// 新写法 - 安全可靠 9let score = 100; 10if (true) { 11 let score = 200; // 块级作用域,互不影响 12 console.log(score); // 输出200 13} 14console.log(score); // 输出100,完美! 15 16// const用于常量,一旦赋值不能修改 17const PI = 3.14159; 18// PI = 3.14; // 这行会报错,保护你的常量不被误改 19 20const user = { name: '小明' }; 21user.name = '小红'; // 这是可以的,修改对象属性 22// user = {}; // 这不行,不能重新赋值 23
看到区别了吗?let和const让变量的作用域更加明确,大大减少了潜在的bug。
箭头函数:简化回调,绑定this
以前写回调函数最头疼的就是this指向问题,箭头函数让这一切变得简单。
1// 老写法 - this指向让人困惑 2function Counter() { 3 this.count = 0; 4 setInterval(function() { 5 this.count++; // 这里的this指向window,不是Counter实例! 6 console.log(this.count); 7 }, 1000); 8} 9 10// 新写法 - 箭头函数自动绑定外部this 11function Counter() { 12 this.count = 0; 13 setInterval(() => { 14 this.count++; // 这里的this正确指向Counter实例 15 console.log(this.count); 16 }, 1000); 17} 18 19// 语法简化对比 20const numbers = [1, 2, 3, 4, 5]; 21 22// 老写法 23const squares = numbers.map(function(num) { 24 return num * num; 25}); 26 27// 新写法 - 简洁明了 28const squares = numbers.map(num => num * num); 29 30// 多参数需要括号 31const sum = numbers.reduce((total, num) => total + num, 0); 32 33// 多行语句需要大括号 34const evenSquares = numbers.map(num => { 35 if (num % 2 === 0) { 36 return num * num; 37 } 38 return null; 39}); 40
箭头函数不仅让代码更简洁,还彻底解决了this绑定的困扰。
模板字符串:告别字符串拼接地狱
还记得用加号拼接字符串的痛苦吗?模板字符串让你重获新生。
1// 老写法 - 眼花缭乱 2const user = { name: '李雷', age: 25 }; 3const message = '你好,' + user.name + '!你今年' + user.age + '岁了。'; 4console.log(message); 5 6// 新写法 - 清晰直观 7const message = `你好,${user.name}!你今年${user.age}岁了。`; 8console.log(message); 9 10// 支持多行字符串,太方便了! 11const emailTemplate = ` 12尊敬的${user.name}: 13 14 感谢您使用我们的服务。 15 您的账户信息: 16 姓名:${user.name} 17 年龄:${user.age} 18 19祝好! 20团队敬上 21`; 22 23// 甚至在${}中可以做运算 24const calculation = `5 + 3 = ${5 + 3}`; // "5 + 3 = 8" 25 26// 调用函数 27function greet(name) { 28 return `Hello, ${name}!`; 29} 30const greeting = `${greet('韩梅梅')} 欢迎回来!`; 31
模板字符串让拼接字符串变得像写正常文本一样自然。
解构赋值:优雅的数据提取
从对象和数组中提取数据再也不需要写一堆临时变量了。
1// 对象解构 - 从user对象中提取name和age 2const user = { 3 name: '王小明', 4 age: 28, 5 email: '[email protected]', 6 address: { 7 city: '北京', 8 district: '朝阳区' 9 } 10}; 11 12// 老写法 - 繁琐重复 13const name = user.name; 14const age = user.age; 15const email = user.email; 16 17// 新写法 - 一行搞定 18const { name, age, email } = user; 19console.log(name, age, email); // 王小明 28 [email protected] 20 21// 重命名变量 22const { name: userName, age: userAge } = user; 23 24// 嵌套解构 25const { address: { city, district } } = user; 26console.log(city, district); // 北京 朝阳区 27 28// 数组解构 29const colors = ['红色', '绿色', '蓝色']; 30 31// 老写法 32const first = colors[0]; 33const second = colors[1]; 34 35// 新写法 36const [first, second, third] = colors; 37console.log(first, second); // 红色 绿色 38 39// 交换变量 - 不再需要临时变量 40let a = 1, b = 2; 41[a, b] = [b, a]; 42console.log(a, b); // 2 1 43 44// 函数参数解构 45function printUser({ name, age }) { 46 console.log(`${name}今年${age}岁`); 47} 48printUser(user); // 王小明今年28岁 49
解构赋值让代码更加简洁,意图更加明确。
默认参数和Rest参数:函数用起来更顺手
给函数参数设置默认值,处理不定数量参数,现在都有优雅的解决方案。
1// 默认参数 - 告别||操作符 2// 老写法 3function greet(name) { 4 name = name || '访客'; 5 return `你好,${name}!`; 6} 7 8// 新写法 9function greet(name = '访客') { 10 return `你好,${name}!`; 11} 12 13console.log(greet()); // 你好,访客! 14console.log(greet('李雷')); // 你好,李雷! 15 16// 默认参数可以是表达式 17function createUser(name, age = 18, registered = Date.now()) { 18 return { name, age, registered }; 19} 20 21// Rest参数 - 处理不定数量参数 22// 老写法 - 使用arguments 23function sum() { 24 let total = 0; 25 for (let i = 0; i < arguments.length; i++) { 26 total += arguments[i]; 27 } 28 return total; 29} 30 31// 新写法 - 使用Rest参数 32function sum(...numbers) { 33 return numbers.reduce((total, num) => total + num, 0); 34} 35 36console.log(sum(1, 2, 3, 4, 5)); // 15 37 38// Rest参数必须是最后一个参数 39function introduce(name, age, ...hobbies) { 40 console.log(`${name}今年${age}岁,爱好:${hobbies.join('、')}`); 41} 42introduce('韩梅梅', 25, '读书', '游泳', '摄影'); // 韩梅梅今年25岁,爱好:读书、游泳、摄影 43 44// 与解构结合使用 45const [first, ...rest] = [1, 2, 3, 4, 5]; 46console.log(first); // 1 47console.log(rest); // [2, 3, 4, 5] 48
这些特性让函数定义更加灵活和健壮。
扩展运算符:数组和对象的瑞士军刀
扩展运算符就像一把万能钥匙,能解决很多日常开发中的常见问题。
1// 数组操作 2const arr1 = [1, 2, 3]; 3const arr2 = [4, 5, 6]; 4 5// 数组合并 - 老写法要用concat 6const combined = [...arr1, ...arr2]; 7console.log(combined); // [1, 2, 3, 4, 5, 6] 8 9// 数组复制 10const arrCopy = [...arr1]; 11console.log(arrCopy); // [1, 2, 3] 12 13// 在特定位置插入元素 14const newArr = [0, ...arr1, 3.5, ...arr2, 7]; 15console.log(newArr); // [0, 1, 2, 3, 3.5, 4, 5, 6, 7] 16 17// 对象操作 18const user = { name: '张三', age: 30 }; 19const preferences = { theme: 'dark', language: 'zh-CN' }; 20 21// 对象合并 22const userWithPrefs = { ...user, ...preferences }; 23console.log(userWithPrefs); // {name: "张三", age: 30, theme: "dark", language: "zh-CN"} 24 25// 对象复制与更新 26const updatedUser = { ...user, age: 31 }; 27console.log(updatedUser); // {name: "张三", age: 31} 28 29// 函数调用时展开数组 30const numbers = [1, 2, 3, 4, 5]; 31console.log(Math.max(...numbers)); // 5,相当于Math.max(1, 2, 3, 4, 5) 32 33// 字符串转数组 34const str = 'hello'; 35const chars = [...str]; 36console.log(chars); // ['h', 'e', 'l', 'l', 'o'] 37
扩展运算符让数据处理变得异常简单和直观。
Promise和async/await:告别回调地狱
这是最重要的改进之一,让异步代码写得像同步代码一样清晰。
1// 老写法 - 回调地狱 2function fetchData(callback) { 3 setTimeout(() => { 4 console.log('数据获取完成'); 5 callback('数据内容'); 6 }, 1000); 7} 8 9fetchData(function(data) { 10 processData(data, function(result) { 11 saveData(result, function() { 12 console.log('全部完成'); 13 }); 14 }); 15}); 16 17// Promise写法 18function fetchData() { 19 return new Promise((resolve, reject) => { 20 setTimeout(() => { 21 console.log('数据获取完成'); 22 resolve('数据内容'); 23 // 如果出错:reject(new Error('获取失败')); 24 }, 1000); 25 }); 26} 27 28fetchData() 29 .then(data => { 30 console.log('处理数据:', data); 31 return processData(data); 32 }) 33 .then(result => { 34 console.log('保存数据:', result); 35 return saveData(result); 36 }) 37 .then(() => { 38 console.log('全部完成'); 39 }) 40 .catch(error => { 41 console.error('出错了:', error); 42 }); 43 44// async/await - 终极解决方案 45async function main() { 46 try { 47 const data = await fetchData(); 48 console.log('处理数据:', data); 49 50 const result = await processData(data); 51 console.log('保存数据:', result); 52 53 await saveData(result); 54 console.log('全部完成'); 55 } catch (error) { 56 console.error('出错了:', error); 57 } 58} 59 60main(); 61 62// 实际示例:顺序执行多个异步操作 63async function getUserData(userId) { 64 try { 65 // 等待用户信息 66 const userInfo = await fetch(`/api/users/${userId}`); 67 const user = await userInfo.json(); 68 69 // 等待用户订单 70 const ordersInfo = await fetch(`/api/users/${userId}/orders`); 71 const orders = await ordersInfo.json(); 72 73 // 等待用户地址 74 const addressInfo = await fetch([`/api/users/${userId}/address`](https://xplanc.org/primers/document/zh/03.HTML/EX.HTML%20%E5%85%83%E7%B4%A0/EX.address.md)); 75 const address = await addressInfo.json(); 76 77 return { 78 user, 79 orders, 80 address 81 }; 82 } catch (error) { 83 console.error('获取用户数据失败:', error); 84 throw error; 85 } 86} 87
async/await让异步代码的可读性达到了全新高度。
模块化:代码组织的艺术
ES6模块让前端工程化成为可能,告别全局变量污染。
1// math.js - 导出模块 2export const PI = 3.14159; 3 4export function add(a, b) { 5 return a + b; 6} 7 8export function multiply(a, b) { 9 return a * b; 10} 11 12// 默认导出 13export default class Calculator { 14 constructor() { 15 this.result = 0; 16 } 17 18 clear() { 19 this.result = 0; 20 } 21} 22 23// app.js - 导入模块 24import Calculator, { PI, add, multiply } from './math.js'; 25 26// 使用导入的功能 27const calc = new Calculator(); 28console.log(PI); // 3.14159 29console.log(add(2, 3)); // 5 30console.log(multiply(4, 5)); // 20 31 32// 批量导入 33import * as math from './math.js'; 34console.log(math.PI); // 3.14159 35console.log(math.add(1, 2)); // 3 36 37// 动态导入 - 按需加载 38async function loadModule() { 39 const module = await import('./math.js'); 40 console.log(module.PI); // 3.14159 41} 42 43// 在实际项目中的使用 44// utils/request.js 45export async function get(url) { 46 const response = await fetch(url); 47 return response.json(); 48} 49 50export async function post(url, data) { 51 const response = await fetch(url, { 52 method: 'POST', 53 headers: { 'Content-Type': 'application/json' }, 54 body: JSON.stringify(data) 55 }); 56 return response.json(); 57} 58 59// components/UserList.js 60import { get } from '../utils/request.js'; 61 62export async function loadUsers() { 63 return await get('/api/users'); 64} 65 66// app.js 67import { loadUsers } from './components/UserList.js'; 68 69async function init() { 70 const users = await loadUsers(); 71 console.log(users); 72} 73
模块化让代码组织更加清晰,依赖关系更加明确。
实际项目重构示例
让我们来看一个真实的重构案例,感受ES6+带来的巨大变化。
1// 老写法 - ES5时代 2var userService = (function() { 3 var apiUrl = 'https://api.example.com'; 4 5 function UserService() { 6 this.users = []; 7 } 8 9 UserService.prototype.fetchUsers = function(callback) { 10 var self = this; 11 $.ajax({ 12 url: apiUrl + '/users', 13 method: 'GET', 14 success: function(data) { 15 self.users = data; 16 callback(null, data); 17 }, 18 error: function(err) { 19 callback(err, null); 20 } 21 }); 22 }; 23 24 UserService.prototype.getUserById = function(id, callback) { 25 var foundUser = null; 26 for (var i = 0; i < this.users.length; i++) { 27 if (this.users[i].id === id) { 28 foundUser = this.users[i]; 29 break; 30 } 31 } 32 callback(null, foundUser); 33 }; 34 35 return UserService; 36})(); 37 38// 新写法 - ES6+现代化 39const API_URL = 'https://api.example.com'; 40 41class UserService { 42 constructor() { 43 this.users = []; 44 } 45 46 async fetchUsers() { 47 try { 48 const response = await fetch(`${API_URL}/users`); 49 if (!response.ok) { 50 throw new Error(`HTTP error! status: ${response.status}`); 51 } 52 this.users = await response.json(); 53 return this.users; 54 } catch (error) { 55 console.error('获取用户列表失败:', error); 56 throw error; 57 } 58 } 59 60 getUserById(id) { 61 return this.users.find(user => user.id === id); 62 } 63 64 // 使用Rest参数和箭头函数 65 getUsersByIds(...ids) { 66 return this.users.filter(user => ids.includes(user.id)); 67 } 68 69 // 使用解构和模板字符串 70 createUser(userData) { 71 const { name, email, age } = userData; 72 const newUser = { 73 id: Date.now().toString(), 74 name, 75 email, 76 age, 77 createdAt: new Date().toISOString() 78 }; 79 80 this.users = [...this.users, newUser]; 81 console.log(`用户 ${name} 创建成功`); 82 return newUser; 83 } 84} 85 86// 使用示例 87async function main() { 88 const userService = new UserService(); 89 90 try { 91 // 获取用户列表 92 const users = await userService.fetchUsers(); 93 console.log('用户列表:', users); 94 95 // 根据ID查找用户 96 const user = userService.getUserById('123'); 97 console.log('找到用户:', user); 98 99 // 批量查找用户 100 const multipleUsers = userService.getUsersByIds('123', '456', '789'); 101 console.log('多个用户:', multipleUsers); 102 103 // 创建新用户 104 const newUser = userService.createUser({ 105 name: '李四', 106 email: '[email protected]', 107 age: 28 108 }); 109 console.log('新用户:', newUser); 110 111 } catch (error) { 112 console.error('操作失败:', error); 113 } 114} 115 116// 立即执行函数 117main(); 118
看到差别了吗?新代码不仅更简洁,而且更易读、更易维护。
写在最后
ES6+的这些特性不是炫技,而是真正能提升代码质量和开发效率的实用工具。从今天开始,试着在你的项目中用起来:
- 从小处开始:先从let/const和箭头函数用起
- 渐进式改进:每次修改代码时,顺便把老语法升级
- 团队约定:和团队成员制定统一的代码规范
记住,好的代码不是写出来给机器看的,而是写出来给人看的。ES6+的特性让我们的代码更加表达意图,减少意外,提升可维护性。
你现在在用哪些ES6+特性?有没有在升级过程中遇到过什么问题?欢迎在评论区分享你的经验和困惑!
升级你的JS技能,从现在开始!
《ES6+革命:8大特性让你的JavaScript代码质量翻倍》 是转载文章,点击查看原文。