V8对高频调用的小型纯函数会自动内联,前提是结构干净、参数稳定且已“热起来”;含try/catch、eval、arguments、动态this绑定或闭包捕获等结构即使很短也会被拒绝;须用--trace-inlining实测验证,而非凭经验猜测。

高频调用的小型纯函数,只要结构干净、参数稳定,V8 大概率会自动内联——但前提是它得先“热起来”,且没踩中那些隐性拒绝条件。盲目拆分或压缩反而破坏内联机会。
如何确认函数是否被 V8 内联
别靠猜,用 --trace-inlining 实测:
- Node.js 启动时加
node --trace-inlining script.js,控制台会输出类似[inlined] add at line 5: inlined into compute或[disabled] reason: too big for inlining (size=124) - 浏览器环境可用
%OptimizeFunctionOnNextCall(func)(仅限开启chrome://flags/#enable-webassembly-simd的调试版 DevTools),再配合--trace-inlining观察 - 注意:冷路径(执行少于 ~10 次)不会触发优化编译,日志里看不到内联记录
哪些结构会让 V8 拒绝内联,哪怕只有 5 行
行数不是硬门槛,AST 可预测性才是。以下情况即使很短也会被跳过:
- 含
try/catch、with、eval—— 直接标记为not inlineable: contains try/catch - 访问
arguments、this动态绑定、或通过字符串访问属性(如obj[expr]) - 闭包捕获外层变量(尤其非
const声明的变量),会增加逃逸分析负担 - 函数体里有
return早退 + 多个分支嵌套,控制流不平坦
拆分函数时最容易忽略的性能陷阱
把一个 25 行线性计算函数硬拆成三个 10 行函数,不等于更优:
- 间接调用开销(call/ret 指令 + 栈帧分配)在热点路径(如
for循环体、requestAnimationFrame回调)里会被放大 - 每个子函数若不再满足「纯」+「固定参数个数」+「无闭包捕获」,可能全都不被内联
- IC(Inline Cache)反馈向量失效:原本对单个函数收集的类型反馈,分散到多个函数后,每处都得重新“热身”
- 更稳妥的做法是:保留主函数骨架,只把真正可复用、且独立无副作用的计算片段抽成
/* @__PURE__ */标记的辅助函数(部分构建工具会传递该提示)
关键不在“多短”,而在“多稳”:参数类型收敛、控制流可推断、无跨上下文依赖。实测比经验更可靠,尤其当逻辑嵌套变深或引入新语法特性时,内联行为可能悄然变化。










