代码显示:
const fn = (length, label, callback) => {
console.time(label);
for (let i = 0; i < length; i++) {
callback && callback(i);
}
console.timeEnd(label);
};
const length = 100000000;
fn(length, "1", () => {}) // very few intervals
fn(length, "2", () => {}) // regular
fn(length, "3", () => {}) // regular
然后我删除了第三个参数callback
,它们的执行时间非常接近..
const fn = (length, label, callback) => {
console.time(label);
for (let i = 0; i < length; i++) {
callback && callback(i);
}
console.timeEnd(label);
};
const length = 100000000;
fn(length, "1") // regular
fn(length, "2") // regular
fn(length, "3") // regular
为什么???
简而言之:这是由于内联造成的。
当诸如 这样的调用
callback()
只看到一个目标函数被调用,并且包含函数(fn
在本例中为“ ”)经过优化时,优化编译器将(通常)决定内联该调用目标。因此在快速版本中,不会执行任何实际调用,而是内联空函数。当您随后调用不同的回调时,旧的优化代码需要被丢弃(“去优化”),因为它现在不正确(如果新回调具有不同的行为),并且在稍后重新优化时,内联启发式方法决定内联多个可能的目标可能不值得付出代价(因为内联虽然有时可以带来很大的性能优势,但也有一定的成本),因此它不会内联任何内容。相反,生成的优化代码现在将执行实际调用,您将看到这样做的成本。
正如 @0stone0 所观察到的,当您在第二次调用时传递相同的
fn
回调时,则无需进行去优化,因此最初生成的优化代码(内联此回调)可以继续使用。使用相同(空)源代码定义三个不同的回调不算作“相同的回调”。值得一提的是,这种影响在微基准测试中最为明显;尽管有时在更接近现实世界的代码中也能看到。这当然是微基准测试常见的陷阱,会产生令人困惑/误导的结果。
在第二个实验中,当没有时
callback
,当然callback &&
表达式的一部分已经退出,并且对的三个调用都fn
不会调用(或内联)任何回调,因为没有回调。