直接在业务函数中写 console.log 会导致日志失控、格式难统一、环境判断冗余、模板重复;withLogger 高阶函数通过声明式封装实现自动日志附着,支持时间戳、函数名、参数快照,且可组合、不侵入业务逻辑。

为什么直接在业务函数里写 console.log 容易失控
日志散落在各处,改格式要全局搜索;环境判断(如 production 禁用 log)靠 if 堆砌;想加时间戳、函数名、参数快照就得重复粘贴模板。这不是记录行为,是在维护日志“副代码”。
高阶函数的核心价值不是炫技,而是把「日志这件事」从「每次都要手动做」变成「声明一次,自动附着」。
用 withLogger 包裹函数是最小可行封装
不依赖框架、不改调用方、不侵入业务逻辑——只需一个返回新函数的函数:
function withLogger(fn, prefix = '') {
return function(...args) {
const now = new Date().toISOString().slice(11, 23);
const fnName = fn.name || 'anonymous';
console.log(`[${now}] ${prefix}${fnName}`, ...args);
return fn.apply(this, args);
};
}
-
withLogger接收原函数和可选前缀,返回带日志的新函数 - 保留
this和所有参数(...args+apply),避免箭头函数丢掉上下文 - 不要在日志里打印
fn.toString()——压缩后函数名丢失,且可能暴露敏感逻辑
生产环境自动禁用日志的两种可靠方式
不能靠开发时删代码,也不能靠运行时频繁判断 process.env.NODE_ENV === 'production'——后者仍会执行函数调用和参数序列化,有性能开销。
- 构建时剔除:Webpack/Vite 的
define插件将__DEV__替换为字面量布尔值,再用if (!__DEV__) return fn;让死代码消除生效 - 运行时短路:在
withLogger开头加if (typeof window !== 'undefined' && !window.__DEBUG__) return fn;,配合页面加载时注入开关 - 别用
console.log代理方案(比如重写console.log)——它无法区分哪些 log 来自业务、哪些来自库,且影响所有第三方代码
需要捕获异常或异步结果时,别硬塞进同一个高阶函数
日志职责一旦混合错误处理或副作用(如上报 Sentry),函数签名和语义就模糊了。拆开更可控:
const logged = withLogger(apiCall); const loggedAndTraced = withTrace(withLogger(apiCall)); // 可组合 const loggedWithErrorCatch = withErrorCatch(withLogger(apiCall));
- 每个高阶函数只专注一件事:
withLogger打印,withErrorCatch捕获并 re-throw,withTrace计时 - 组合顺序很重要:想先捕获错误再打印,就写
withLogger(withErrorCatch(fn));反之则反过来 - 避免在
withLogger内部 try/catch——它不该改变原函数的抛错行为,那是withErrorCatch的事
真正难的不是写出高阶函数,而是想清楚「日志」在当前系统里到底该承担什么边界:是调试快照?是可观测性数据源?还是用户行为埋点?不同目标,封装粒度和附加逻辑完全不同。

















