Chrome中for...in循环首次运行极快、后续变慢的原因探究
这个问题其实涉及到Chrome底层V8引擎的即时编译(JIT)优化逻辑,我来帮你理清楚背后的原因:
先明确你的测试背景:在Chrome 146.0.7680.31 Beta版本中,你对比了两种统计JavaScript对象属性数量的方案:
- 用
for...in循环遍历每个属性并递增计数器 - 调用
Object.keys(obj).length直接获取自有属性数量
你进行了两次1亿次迭代的测试,结果差异非常显著:
首次1亿次迭代测试结果
for...in循环:0.531秒Object.keys(obj).length:0.955秒
第二次1亿次迭代测试结果
for...in循环:3.361秒Object.keys(obj).length:0.994秒
造成这种“首快后慢”反差的核心原因,在于V8引擎的优化策略差异:
首次运行的激进乐观优化
V8的JIT编译器在第一次执行代码时,会做快速的乐观假设。对于for...in循环,它会默认你的测试对象结构极简——没有原型链上的继承属性、属性数量固定且可预测,于是生成了高度精简的机器码,跳过了一些不必要的检查(比如原型链属性的遍历验证),这就是第一次for...in跑得飞快的原因。后续运行的去优化(Deoptimization)
当你第二次运行这段代码时,V8会重新评估之前的优化是否合理。尽管你的测试对象没有变化,但for...in的语法特性要求它必须兼容原型链属性的遍历场景,引擎会判定之前的激进优化存在风险,于是触发去优化操作:丢弃之前生成的高效机器码,转而使用更通用、更严谨的执行逻辑,这就导致for...in的执行速度大幅下降。Object.keys的稳定内置优化
而Object.keys(obj).length是V8专门深度优化过的内置API,它直接读取对象内部存储的自有属性数量元数据,不需要实际遍历属性,也不需要处理原型链的额外检查,所以无论运行多少次,它的性能都非常稳定,波动极小。
总结一下:for...in的首次快是引擎做了“偷懒式”的激进优化,后续慢是回归了严谨的通用逻辑;而Object.keys从一开始就采用了最稳定高效的实现方式,所以性能始终保持一致。
备注:内容来源于stack exchange,提问作者user3163495

