工具函数库:你的代码里有多少个重复的 格式化
翻翻一个项目,你大概率会撞见几个 同胞兄弟:
formatDate、formatDateTime、toYmd、padZero……名字不同,干的事一模一样。这篇文章讲的是怎么把这些散落的碎片收敛成一个像样的工具函数库 - 既要避免重复造轮子,又要防止为了 复用 而过度抽象。重点讲清楚: 哪些该抽、哪些不该抽、怎么抽得 顺手不憋屈 。
一、先诊断:什么样的代码值得抽成工具
不是所有重复都要抽,乱抽反而制造理解负担。判断 该不该抽 的三个标准:
- 纯函数 :没有副作用、不依赖
this和外部状态,同样的输入给一样的输出 - 最适合抽。 - 高频复用 :至少在两三处以上 真的在不同地方用到 ,不是仅在一个文件里出现。
- 语义稳定 :做的事情 含义明确且不变 ,不是临时候补的逻辑。
反面例子:你只在两处用到一个临时函数,但两处没有任何共同语义,硬抽出来反而增加跳转。 工具库的价值在 复用 ,不是你造了一个栈。
二、最典型的收割对象:日期格式化
日期格式化是用得最多、最该收的工具。一个项目里常见的需求:
- 时间戳 →
YYYY-MM-DD(日期) - 时间戳/时间 →
YYYY-MM-DD HH:mm:ss(日期时间) - 因为时区问题,同一个时间在不同服务器上显示差 8/9 小时
如果每个人自己写一套格式化脚本,各写各的,很快你会发现 2026/09/11 和 2026-09-11 并存、 24 小时制 和 12 小时制 并存。工具函数库的使命就是把它们钉在一套默认格式上。
一个通用的轻量格式化函数(伪代码,逻辑简单):
// 一个足够通用的格式化,不依赖第三方库也能看懂原理
function formatDate(date, pattern = 'YYYY-MM-DD') {
if (!date) return '';
const d = new Date(date);
// 手动把 NaN 判掉,兼容 null/undefined
if (Number.isNaN(d.getTime())) return '';
const map = {
YYYY: d.getFullYear(),
MM: String(d.getMonth() + 1).padStart(2, '0'),
DD: String(d.getDate()).padStart(2, '0'),
HH: String(d.getHours()).padStart(2, '0'),
mm: String(d.getMinutes()).padStart(2, '0'),
ss: String(d.getSeconds()).padStart(2, '0'),
};
return pattern.replace(/YYYY|MM|DD|HH|mm|ss/g, m => map[m]);
}这样全项目只要调 formatDate(date, 'YYYY-MM-DD HH:mm:ss') ,格式和时区都由这一个函数兜底,而不是每处各写各的。
三、判断 / 转换类:isEmpty、toNumber 这类 软工具
除了日期,还有一批 判断类 工具高频复用:
// 判断空:兼容 null / undefined / 空串 / 空数组 / 空对象
function isEmpty(value) {
if (value === null || value === undefined) return true;
if (typeof value === 'string') return value.trim() === '';
if (Array.isArray(value)) return value.length === 0;
if (typeof value === 'object') return Object.keys(value).length === 0;
return false;
}
// 安全的字符串化:避免 undefined 被直接拼进 SQL/写法
function safeString(v) { return v === undefined || v === null ? '' : String(v); }这种「软工具」的价值在于把一堆 易错边界 ( null 、 undefined 、空数组、空对象)集中兜一遍,避免每个人在不同地方各查各的。
四、安全与加密类:md5/sha1/签名 一定要收集中管
像 MD5、SHA1、签名生成这类涉及安全逻辑的工具, 更该集中 ,而不是每个模块各自实现:
const crypto = require('crypto');
function md5(str) {
return crypto.createHash('md5').update(String(str)).digest('hex').toUpperCase();
}
function sha1(str) {
return crypto.createHash('sha1').update(String(str)).digest('hex').toLowerCase();
}
// 从一个对象构造签名用的 参数排序串 ,接口签名常用
function buildSignText(params, exclude = ['sign']) {
return Object.keys(params)
.filter(k => exclude.indexOf(k) === -1 && params[k] !== undefined && params[k] !== '')
.sort()
.map(k => `${k}=${params[k]}`)
.join('&');
}集中管理的价值: 一旦签名规则需要调整(比如加盐、换算法),只改一处 ,而不是去翻几十个接口各自手写的加密。安全相关不外泄,也少一份 这处用了 sha1 那处用了 md5 的隐患。
五、什么不该抽进工具库(防止过度设计)
工具库最容易跑偏成 垃圾桶 ,什么都往里塞。以下情况 别抽 :
- 只在一个地方用到 的 helper:抽了反而把局部逻辑 移到外面 ,跨文件才能看到,增加跳转成本。
- 强依赖业务字段 的逻辑(比如 根据状态返回颜色 这种业务映射),它更适合放在业务模块里,而不是当通用工具。
- 还没想清楚是否复用 就抽:先用两三次,等 真的重复得三处以上、语义也稳定了 再抽。提前抽象往往抽错方向。
工具库的适度目标: 收那些 两处以上、语义稳定、无副作用 的纯函数 。宁缺毋滥。
六、组织方式与命名
工具函数库是全市共用,命名和目录要稳定:
- 目录组织 :按 维度 分文件 -
date.js、string.js、crypto.js、check.js、tree.js……而不是一把梭进utils/index。 - 命名风格 :文件名
kebab-case,函数用camelCase、有清晰的动词(isEmpty/formatDate/buildSignText)。 - 导出 :统一从一个入口导出,其他人
import { isEmpty, formatDate } from '@/utils',一处 destructure 拿完。 - 文档/注释 :这种纯函数特别适合 一行注释 + 示例 ,让人 copy 就能懂。
七、好不好用,看这两个信号
抽完之后怎么知道抽对了:
- 新增业务时是否 少写了一小段重复 :如果新需求能直接用已有工具,说明抽对了。
- 别人是否愿意用 :如果大家宁可在自己文件里重写,说明你的工具要么命名难找、要么参数反直觉、要么根本不该复用 - 该观察一下。
八、小结
工具函数库不是 代码多伟大 ,而是 把重复且稳定的纯函数,收敛成一个可以被全项目复用的稳定出口 :
- 日期/判断/安全/签名这些高频 纯函数 ,最适合抽;
- 只有 跨处复用、语义稳定、无副作用 才算值得抽,否则就成了过度设计;
- 按业务建文件、统一出口、写清注释,别让工具库变成 不烂的垃圾堆 ;
- 通过 新需求能否直接复用 来自证它的价值。
到这里, 后端分层与工程规范 这一整组我们就走完了(统一响应、全局异常、迁移、关联查询、工具库)。下一组我们进入 接口交互 :GET/POST/PUT 的语义、路由顺序、参数校验的真谛,还有 JWT 鉴权怎么和白名单拆开。




