代码层面的优化:让每一行代码都跑得更快
代码优化,说白了就是让计算机少干活。我见过太多团队,一上来就搞架构升级、加机器,结果回头一看,代码里全是坑。今天咱们聊聊代码层面那些立竿见影的优化手段。
算法复杂度分析:选对算法,事半功倍
我个人习惯,写代码前先想清楚复杂度。不是所有场景都需要O(1),但至少别写出O(n²)去处理百万级数据。
举个例子,我接手过一个报表系统,每次生成都要30秒。一查,核心逻辑是个双层循环:
// 优化前:O(n*m) 的噩梦
for (let i = 0; i < users.length; i++) {
for (let j = 0; j < orders.length; j++) {
if (orders[j].userId === users[i].id) {
// 处理逻辑
}
}
}
改成哈希表后,直接降到O(n+m):
// 优化后:O(n+m)
const userMap = new Map();
users.forEach(u => userMap.set(u.id, u));
orders.forEach(order => {
const user = userMap.get(order.userId);
if (user) {
// 处理逻辑
}
});
运行时间从30秒降到了0.8秒。你想想看,只是换了个数据结构,效果天差地别。
核心原则:能用O(log n)别用O(n),能用O(n)别用O(n²)。数据量每翻10倍,复杂度差一级,性能就差一个数量级。
减少循环嵌套:扁平化才是王道
我在项目中遇到过一段代码,三层嵌套循环处理订单数据。当时我就觉得不对劲,一分析,复杂度O(n³)。
为什么会这样?很多时候是因为我们习惯性用循环解决问题。其实很多嵌套循环可以用以下方式替代:
- 提前过滤:先filter掉不需要的数据,再处理
- 空间换时间:用Map、Set等结构减少查找次数
- 分批处理:大数据量时,拆成小批次并行处理
我曾经优化过一个库存扣减逻辑,原来是这样:
// 三层嵌套,不忍直视
for (let warehouse of warehouses) {
for (let shelf of warehouse.shelves) {
for (let item of shelf.items) {
if (item.sku === targetSku) {
// 扣减库存
}
}
}
}
改成用SKU索引后:
// 一层循环 + 索引
const skuIndex = buildSkuIndex(warehouses);
const target = skuIndex.get(targetSku);
if (target) {
target.quantity -= amount;
}
嗯,这里要注意:不是所有嵌套都能消除,但至少可以降到两层以内。超过两层的循环,基本就是代码坏味道了。
使用高效的数据结构:选对容器,事半功倍
很多开发者对数据结构的理解停留在「能用就行」。我见过有人用数组做频繁的插入删除操作,也见过用对象当队列用的。这些习惯,在数据量小的时候没问题,一旦上了规模,就是性能杀手。
| 操作场景 | 推荐结构 | 不推荐结构 | 原因 |
|---|---|---|---|
| 频繁查找 | Map / Set | Array | Map查找O(1),Array查找O(n) |
| 频繁插入/删除 | LinkedList | Array | Array插入删除需要移动元素 |
| 先进先出 | Queue | Array.shift() | shift()会重新索引所有元素 |
| 去重 | Set | Array + indexOf | Set去重O(n),indexOf去重O(n²) |
我记得有一次优化一个实时推荐系统,原来用数组存用户行为数据,每次查找都要遍历。换成Map之后,响应时间从200ms降到了5ms。说白了,数据结构选对了,性能优化就完成了一半。
小技巧:JavaScript中,for...of遍历Map比Object.keys()遍历对象快30%左右。遇到频繁遍历的场景,优先考虑Map。
避免重复计算:缓存是好朋友
重复计算是代码里最常见的性能浪费。我见过一个计算订单总价的函数,每次调用都重新计算所有商品的折扣。其实折扣信息一周才变一次,完全没必要每次都算。
常见的重复计算场景:
- 循环内的不变计算:把循环外的计算提到外面
- 重复的函数调用:结果缓存起来,下次直接用
- 重复的DOM查询:用变量保存查询结果
- 重复的API调用:加一层缓存层
// 优化前:每次循环都计算一次
for (let i = 0; i < items.length; i++) {
const discount = getDiscount(items[i].category); // 每次都要查数据库
items[i].price = items[i].price * discount;
}
// 优化后:缓存折扣信息
const discountCache = new Map();
for (let i = 0; i < items.length; i++) {
const category = items[i].category;
if (!discountCache.has(category)) {
discountCache.set(category, getDiscount(category));
}
items[i].price = items[i].price * discountCache.get(category);
}
我曾经犯过一个错误:在递归函数里重复计算斐波那契数列。n=40的时候,程序直接卡死。加上记忆化缓存后,瞬间出结果。避坑指南:递归函数一定要考虑重复计算的问题,加个缓存往往能指数级提升性能。
注意:缓存虽好,但要注意缓存失效策略。我见过有人把用户信息缓存了24小时,结果用户改了密码还在用旧数据。缓存一定要设置合理的过期时间。
知识体系总览
下面这张图总结了代码层面优化的核心思路,我把它画成了流程图,方便你理解各个优化点之间的关系:
这张图把四个优化方向串起来了。你从中心出发,沿着任意一条分支走到底,都能找到具体的优化手段。我个人建议,每次做代码审查时,就拿这张图对照一下,看看代码踩了哪些坑。
最后说一句:代码优化不是一蹴而就的事。我习惯在每次提交代码前,花5分钟检查一下有没有明显的性能问题。养成习惯后,你会发现写出高性能代码其实没那么难。