导读:本期聚焦于安然创作的《JavaScript代码有哪些常见坏味道?如何通过重构让代码更易维护?》,敬请观看详情。一段if-else嵌套五六层的函数,一个超过八百行的组件文件,这些代码写的时候顺手,改起来却让人头疼。代码坏味道是重构的信号灯,它能提前告诉我们哪些地方正在腐化。本文围绕JavaScript项目中最常见的几类坏味道展开,包括超长函数、重复代码、嵌套过深的条件逻辑、魔法值滥用以及全局状态的失控,逐一分析它们的成因和危害,并给出对应的重构手法,比如提取函数、卫语句提前返回、用策略模式或映射表替代条件分支、用常量和枚举替代魔法数字等。文章还结合具体代码示例演示重构前后的对比,帮助你在日常开发中养成识别坏味道的习惯,让代码在持续小步重构中保持整洁与可扩展。

坏味道这个词出自Martin Fowler的重构经典著作,它指的不是bug,而是代码中那些让人隐隐不安的结构问题。坏味道本身不会让程序崩溃,但它们是代码正在腐化的早期信号,等到积累到一定程度,任何一个需求变更都可能引发连锁修改。JavaScript因为语法灵活、类型宽松,特别容易滋生各种坏味道,比如随意挂在window上的全局变量、回调地狱、被滥用的可选链等。这篇文章就来梳理JavaScript项目里最常见的几类坏味道,并给出切实可行的重构手法。

JavaScript代码有哪些常见坏味道?如何通过重构让代码更易维护?

超长函数与嵌套条件:最直观的坏味道

超长函数几乎是所有遗留代码的通病。一个函数超过八十行,通常意味着它承担了不止一个职责。刚开始可能只是处理一个表单提交,后来同事往里加了一段校验,再后来又塞进去一段埋点上报,半年后没人敢动它。判断标准其实很简单:如果你无法用一个动词短语给函数命名,或者函数名里出现了“和”“并且”这类字眼,它大概率做了太多事。

对付超长函数最有效的手法是提取函数(Extract Function)。把函数内一段有独立意图的代码抽成一个小函数,起一个表达意图的名字。看下面这个典型例子:

// 重构前:一个函数干了三件事
function handleOrderSubmit(order) {
  // 校验
  if (!order.items || order.items.length === 0) {
    alert("购物车为空");
    return;
  }
  if (!order.address) {
    alert("请填写地址");
    return;
  }
  // 计算价格
  let total = 0;
  for (const item of order.items) {
    total += item.price * item.quantity;
  }
  if (order.coupon) {
    total = total * 0.9;
  }
  // 提交
  fetch("/api/order", {
    method: "POST",
    body: JSON.stringify({ ...order, total })
  });
}

重构后,主函数变成了一段几乎不需要注释的“目录”,每一行都在描述做什么而不是怎么做:

// 重构后:意图一目了然
function handleOrderSubmit(order) {
  if (!validateOrder(order)) return;
  const total = calcOrderTotal(order);
  submitOrder(order, total);
}

function validateOrder(order) {
  if (!order.items || order.items.length === 0) {
    alert("购物车为空");
    return false;
  }
  if (!order.address) {
    alert("请填写地址");
    return false;
  }
  return true;
}

function calcOrderTotal(order) {
  const subtotal = order.items.reduce(
    (sum, item) => sum + item.price * item.quantity, 0
  );
  return order.coupon ? subtotal * 0.9 : subtotal;
}

嵌套过深的条件判断是另一类高频坏味道。当if里套着if,再套着循环和try-catch时,人的大脑已经很难跟踪执行路径了。最实用的技巧是卫语句:把异常情况、边界情况放在函数开头提前返回,让主干逻辑保持在最外层。另外一个思路是用多态或映射表消灭switch分支,这一点在下一节展开。

重复代码与魔法值:隐蔽但代价高昂

重复代码(Duplicated Code)是最臭的坏味道之一,因为它是bug的温床。当同一段逻辑复制粘贴到三个地方,某天需求变化时你只改了两处,第三处就成了隐患。JavaScript里的重复不只出现在同一个文件,前后端的字段校验规则、多个组件里相似的格式化函数、甚至copy自Stack Overflow的工具函数,都会形成散落各处的重复。

处理重复的基本手法是提取函数后统一引用,更进一步可以抽取成独立的工具模块。对于结构相似但细节不同的片段,可以用回调或配置参数做参数化。举个例子,多个页面都有“分页加载数据”的逻辑,与其每个页面写一遍,不如抽成一个带配置的公共函数:

// 提取后的通用分页加载器
function createPaginator({ fetchPage, pageSize = 20 }) {
  let page = 1;
  let hasMore = true;
  return {
    async loadMore() {
      if (!hasMore) return [];
      const list = await fetchPage(page, pageSize);
      page += 1;
      hasMore = list.length === pageSize;
      return list;
    }
  };
}

魔法值(Magic Number)则是另一种容易被忽视的坏味道。代码里突然出现一个if (status === 3),三个月后没人记得3代表什么。改进方式很简单:用具名的常量或枚举对象替代裸数字和裸字符串:

// 重构前
if (user.role === 2) { /* ... */ }
setTimeout(loadData, 5000);

// 重构后
const USER_ROLE = { ADMIN: 1, EDITOR: 2, VIEWER: 3 };
const REFRESH_INTERVAL_MS = 5000;

if (user.role === USER_ROLE.EDITOR) { /* ... */ }
setTimeout(loadData, REFRESH_INTERVAL_MS);

常量提取看似小事,但它把“知识”从人的脑袋里搬进了代码里。新人接手项目时,USER_ROLE.EDITOR远比2友好得多。在较新的项目中,也可以直接用TypeScript的枚举或联合类型,在编译期就获得约束。

全局状态与条件逻辑失控:架构层面的坏味道

前面的坏味道多发生在函数级别,而全局状态的滥用则是文件级别甚至架构级别的问题。JavaScript里随手一个不带声明关键字的赋值就会污染全局作用域,前端项目中的全局事件总线、挂在window上的缓存对象、被到处直接读写的模块级变量,都会让数据流向变得难以追踪。谁改了这个值?什么时候改的?为什么界面显示不对?这类问题排查起来极其耗时。

改进方向是收敛状态的所有权。把状态封装到类或模块内部,只暴露必要的读写方法;在前端框架项目中,优先使用框架提供的状态管理方案,让变更集中且可追踪。下面是一个简单的封装示例:

// 重构前:全局变量随处可改
let cartItems = [];

function addItem(item) { cartItems.push(item); }
function somewhere() { cartItems = []; /* 悄悄清空 */ }

// 重构后:状态私有化,通过方法访问
const cart = (() => {
  let items = []; // 私有状态
  return {
    add(item) { items = [...items, item]; },
    clear() { items = []; },
    count() { return items.length; }
  };
})();

条件逻辑失控同样值得警惕。当switch语句膨胀到十几个case,或者同一个状态判断散落在多个函数里,就该考虑用映射表或策略模式重构了。映射表是JavaScript里性价比最高的手段,一个普通对象就能替代大段分支:

// 重构前: switch 堆积
function getDiscount(userType) {
  switch (userType) {
    case "vip": return 0.8;
    case "svip": return 0.7;
    case "staff": return 0.6;
    default: return 1;
  }
}

// 重构后:数据驱动,扩展只需加一行
const DISCOUNT_MAP = {
  vip: 0.8,
  svip: 0.7,
  staff: 0.6
};
const getDiscount = (type) => DISCOUNT_MAP[type] ?? 1;

需要强调的是,重构不是一次性运动,而是持续的小步实践。识别出一个坏味道,就做一次小范围的重构,配好测试再动手,改完立刻验证。Fowler那句“让营地比你来时更干净”用在代码上再合适不过:每次提交前顺手提取一个函数、消除一处重复、命名一个魔法值,日积月累,整个代码库就会一直保持在可以快速响应变化的状态。

JavaScript重构代码坏味道代码可维护性修改时间:2026-09-08 06:18:41

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/20260908/52631.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。