JavaScript闭包性能

23

我已经在JavaScript方面工作了一段时间,通常会做类似于这样的事情,只是为了缓存深层结构或“命名空间”内声明的函数属性的值。

//global scope
(function ($, lib) {
  //function scope 1
  var format = lib.format, // instead of calling lib.format all the time just call format
    touch = lib.pointer.touch, //instead of calling lib.pointer.touch each time just touch
    $doc = $(document),
    log = logger.log; //not console log...


  $doc.on('app:ready', function () {
    //function scope 2
    $doc.on('some:event', function (e) {
      //function scope 3
      //use the cached variables
      log(format('{0} was triggered on the $doc object', e.type);
    });

    $doc.on(touch, function (e) {
      //function scope 3
      log(format('this should be {1} and is... {0} ', e.type, touch);
    });
  }); 
}(jQuery, lib));

我这样做是因为:
  • 即使在功能强大、带有花哨自动完成的 IDE 可以帮助,写 touch 似乎比写 lib.pointer.touch 更吸引人,因为我很懒。
  • 一个最小化器可以将单个私有变量转换为单个字母变量,所以对我来说也有意义(我知道,我知道,不要过早优化,但我想这似乎是安全的)。

所有以这种方式编写的代码似乎在移动设备和桌面浏览器上表现良好,所以它似乎是一种安全的实践(在“实践”中,双关语)。但由于这依赖于闭包,而内部函数必须创建一个闭包来保存它声明时的上下文,所以我在想...

  • if a function does not uses variables from the outside context (free variables)... is the closure context still saved? (or if i a tree falls in the wood and nobody is there to hear it, does it still make the crash sound? hehe) I'm aware that this could vary between javascript engines, because ECMA mention nothing about if it is required to save the context or not when variables from the outside are not accessed.

  • if the above expression is true... will this block of code be more efficient?

    //global scope
    (function ($, lib) {
      //function scope 1
      var format = lib.format,
        touch = lib.pointer.touch,
        $doc = $(document),
        log = console.log;
    
      $doc.on('app:ready', function () {
    
        (function ($doc, touch, lib, format) {
          // since all the variables are provided as arguments in this function
          // there is no need to save them to the [[scope]] of this function
          // because they are local to this self invoking function now
          $doc.on('some:event', function (e) {
            //function scope 3
            //use the cached variables
            log(format('{0} was triggered on the $doc object', e.type);
          });
    
          $doc.on(touch, function (e) {
            //function scope 3
            log(format('this should be {1} and is... {0} ', e.type, touch);
          });
        }($doc, touch, lib, format));      
    
      }); 
    
    }(jQuery, lib));
    

将这些变量传递给立即调用的函数是更有效的吗?创建新函数的成本是否会对代码产生任何影响(负面或积极)?

  • 我如何以可靠的方式正确测量我的JavaScript库的内存消耗?我有 100x 小型 JavaScript 模块,都在立即自执行函数内部,大多数情况下是为了避免变量泄漏到全局上下文中。因此,它们都被包装在非常类似于上面提到的代码块的模块中。

  • 即使这意味着我必须在更接近将要使用它们的地方重复变量声明,将变量缓存得更近是否会产生更好的效果?

  • 我有一种感觉,在当前本地上下文中找不到变量时,引擎将首先查找父级作用域并迭代该级别的所有变量...每个级别的变量越多,查找变量的性能可能就越差。

  • 从内部闭包中尝试查找未定义的变量将是最昂贵的,因为根据定义,将首先在父级范围中搜索变量,直到全局范围,并且找不到它将迫使引擎最终到达全局范围。这是真的吗?引擎是否优化了这种查找?

在最终...我知道我不想实现我的代码作为第二个例子,因为这会使代码更难阅读,而且我对使用第一种方法生成的最终输出大小较小感到舒适。我的问题是出于好奇心和试图更好地理解JavaScript的这个非常好的特性。
根据这个测试...

http://jsperf.com/closures-js

似乎第二种方法更快。但只有在迭代大量次数时才明显...现在我的代码没有执行那么多次迭代...但可能因为我的编码方式而消耗了更多的内存...
更新:有人指出这个问题太大了。很抱歉,我会尝试分成小部分。正如我所说,这个问题主要是出于好奇心,即使在移动设备上性能也几乎可以忽略不计。谢谢你们的反馈。

我没有详细的答案给你,但是我几天前发现了这个链接:http://www.developer.nokia.com/Community/Wiki/JavaScript_Performance_Best_Practices。 - Luca Rainone
3
这是一个非常长的问题,你最好把它分开来问。 - Bergi
3
Chrome 会优化闭包,你可以通过调试器验证。如果在闭包中没有被使用的变量,你将无法看到它们的值。不过,你的问题有点多,每篇帖子应该只包含一个问题,这样对其他用户最有帮助。 - Ruan Mendes
我猜 $doc = $doc 是为了优化将全局变量引入到本地作用域中? - Ruan Mendes
2
关键短语是“它会使代码更难阅读”和“只有在迭代了疯狂的次数后才能显现出来”。你必须自己决定是否值得牺牲可读性以换取微秒级的收益。 - Dennis
显示剩余4条评论
1个回答

18

我认为这是过早的优化。你已经知道在大多数情况下性能不是问题。即使在紧密循环中,性能也不会那么糟糕。让JavaScript引擎自己进行优化,就像Chrome已经开始做的那样,通过从闭包中删除不需要的变量。

重要的一点是,不要通过不必要的优化使代码难以阅读。你的示例需要更多的代码,阻碍了开发。在某些情况下,我们必须使代码更难以阅读,因为我们知道应用程序的某个特定部分具有更多的内存/性能需求,但只有在那时才需要这样做。

如果在以下代码中添加断点(在Chrome中),你会看到world变量已被优化掉,在Scope Variables下的“Closure”节点中查看 http://jsfiddle.net/4J6JP/1/

enter image description here.

(function(){
   var hello = "hello", world="world";
    setTimeout(function(){
        debugger;
        console.log(hello);
    });
})()
注意,如果在该内部函数中添加eval,则所有都无法保证,并且无法优化闭包。

2
谢谢,那么对于你来说,除非闭包真的成为问题,否则关注闭包的性能就没有太大意义了?而浏览器似乎正在很好地优化闭包。因此,在自调用函数中有100多个模块不应该是一个问题,对吧? - roy riojas
4
无论用多少数字,我认为变化并不明显。在遇到问题前,坚持使用最易读的方式。只有当你处理特别占用内存或性能的代码段时,才会注意到这种变化。 - Ruan Mendes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接