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