为什么在嵌套函数之外声明一个计数器变量会使循环慢5倍?

时间:2018-03-14 09:00:38

标签: javascript optimization micro-optimization

我正在寻找一些我正在重新审视的JavaScript遗留代码的微优化,并注意到在大多数频繁调用的for循环中,计数器在全局范围内声明一次,在使用它们的函数之外。我很好奇这是否确实是一个优化,因此我在JavaScript中创建了以下测试用例:

var tmp = 0;

function test(){

    let j = 0;

    function letItBe(){

        for(j = 0; j < 1000; j++){
            tmp = Math.pow(j, 2);
        }
    }

    function letItNotBe(){
        for(let l = 0; l < 1000; l++){
            tmp = Math.pow(l, 2);
        }
    }

    console.time("let it be");
    for(var i =0; i < 10000; i++){

        letItBe();
    }
    console.timeEnd("let it be");


    console.time("let it not be");
    for(var i =0; i < 10000; i++){

        letItNotBe();
    }
    console.timeEnd("let it not be");
}

test();

在Chrome,Firefox和NodeJS中,letItNotBe()的运行速度明显快于letItBe()

铬: enter image description here

的NodeJS:

enter image description here

使用 var 更改没有任何区别。

最初我的逻辑是,每次调用一个函数时声明一个新的计数器变量确实比最初声明一个变量然后简单地重置为0要慢。但是,事实证明它是相当的对立面和区别在执行时间是非常重要的。

我的简单解释是,当计数器变量被声明为使用它的函数时,JS转换器需要以某种方式引用此变量。并且因为它在父范围内,所以在递增时需要更多的执行来引用它。然而,这只是盲目的猜测。

任何人都可以给出任何有意义的解释,为什么会发生这种情况,因为除了我已经拥有的测试之外,我需要重构代码并给出一个有意义的解释mysefl :)谢谢。

1 个答案:

答案 0 :(得分:9)

我读过一本书High Performance JavaScript,作者在第2章“数据访问” - “管理范围”部分 - “标识符解析性能”部分对此进行了解释。

  

标识符解析不是免费的,因为实际上没有计算机操作   真的没有某种性能开销。更深入   执行上下文的作用域链存在标识符,速度较慢   它是访问读取和写入。因此,当地   变量总是访问函数内部最快的,   而全局变量通常是最慢的(优化   JavaScript引擎能够在某些情况下调整它。)

     

...

     

所有浏览器的总体趋势是更深入   范围链存在标识符,从中读取或更慢   写给。

     

...

     

根据这些信息,建议每次使用局部变量   可以在没有优化的情况下提高浏   JavaScript引擎。一个好的经验法则是始终存储   如果多次使用局部变量中的超出范围值   在一个函数内。

在您的情况下,letItBeletItNotBe以相同的方式工作,使用相同的超出范围的tmp变量,并且它们都是闭包。唯一的区别是for循环的计数器变量:

  • 变量j是为函数test()定义的,它是函数letItBe()的“超出范围”,因此执行letItBe()将导致引擎执行更多工作关于标识符解析
  • 变量lfor循环的范围内定义(请参阅let keyword in the for loop),因此分辨率更快