更深入地了解Javascript中的闭包

时间:2013-04-04 02:18:57

标签: javascript closures

我正在阅读答案的评论并看到this comment

  

[闭包]不会持久保存foo的状态,因为它创建了一个特殊的范围,包含(1)返回的函数和(2)返回时引用的所有外部变量。这个特殊范围称为闭包。

好的,到目前为止一切顺利。现在这里是我不知道的有趣部分:

  

一个例子......如果你在foo中定义了另一个在返回函数中未被引用的var,它就不会存在于闭包范围内。

我想这是有道理的,但除了内存使用/性能之外,它还有什么影响呢?

问题 - 如果范围内的所有变量都包含在闭包中,那么我可以做什么呢?我不能用当前模型做什么?

4 个答案:

答案 0 :(得分:11)

我认为你太过于字面意思。评论只是说你不能在函数范围之外访问它(它不能公开访问),而不是它在函数中根本不可用。无论如何,返回的函数都可以访问所有外部函数范围。如果内部函数没有提供访问它的方法,你就无法访问外部函数之外的那个范围。

例如,此表达式的计算结果为4:

function testClosure(){
 var x = 2;
    return function(y){
        alert(eval(y));
    }

}

var closure = testClosure();

closure("x+2");  //4

http://jsfiddle.net/dmRcH/

所以尽管没有被直接引用,x仍然可用

进一步研究

看起来chrome和firefox至少会尝试优化这个,因为如果你没有提供任何方式来引用x变量,它就不会显示为在调试器中可用。在封闭内部使用断点运行此操作会在Chrome 26和Firefox 18上显示x不可用。

http://jsfiddle.net/FgekX/1/

但那只是一个内存管理细节,而不是语言的相关属性。如果有任何可能的方法可以引用变量,那么它就会被传递,我怀疑其他浏览器可能不会以相同的方式对其进行优化。编码规范总是比实现更好。在这种情况下,虽然规则确实是:“如果您有任何可能的方式来访问它,它将可用”。而且,不要使用eval,因为它确实会使您的代码无法优化任何内容

答案 1 :(得分:3)

  

如果你在foo中定义了另一个未在return函数中引用的var,它将不存在于闭包范围内。

这不完全准确;变量是闭包范围的一部分,即使它不能直接在函数内部引用(通过查看函数代码)。不同之处在于引擎如何优化未使用的变量。

例如,当您使用DOM元素时,已知闭包范围中未使用的变量会导致内存泄漏(在某些引擎上)。以这个经典的例子为例:

function addHandler() {
    var el = document.getElementById('el');
    el.onclick = function() {
        this.style.backgroundColor = 'red';
    }
}

Source

在上面的代码中,内存被泄露(至少在IE和Mozilla中),因为el是单击处理函数的闭包范围的一部分,即使它没有被引用;这导致循环引用无法被删除,因为它们的垃圾收集基于引用计数。

另一方面,

Chrome使用different garbage collector

  

在V8中,对象堆被分为两部分:创建对象的新空间和提升垃圾收集周期的对象的旧空间。如果在垃圾收集周期中移动对象,V8将更新指向该对象的所有指针。

这也称为世代或ephemeral garbage collector。虽然更复杂,但这种类型的垃圾收集器可以更准确地确定是否使用了变量。

答案 2 :(得分:1)

JavaScript没有固有的隐私感,因此我们使用函数范围(闭包)来模拟此功能。

你引用的SO答案是模块模式的一个例子,Addy Osmani在Learning JavaScript Design Patterns中解释了重要性:

  

模块模式封装了“隐私”,状态和组织   使用闭包。它提供了一种包含公共和公共混合的方式   私有方法和变量,保护件免于泄漏   全球范围和意外碰撞另一个开发商的   接口。使用此模式,只返回一个公共API,保留   封闭内的其他一切都是私密的。

答案 3 :(得分:1)

    Please have a look below code:

    for(var i=0; i< 5; i++){            
                setTimeout(function(){
                    console.log(i);
                },1000);                        
            }

   Here what will be output? 0,1,2,3,4 not that will be 5,5,5,5,5 because of closure

   So how it will solve? Answer is below:

   for(var i=0; i< 5; i++){
            (function(j){     //using IIFE           
                setTimeout(function(){
                    console.log(j);
                },1000);
            })(i);          
        }

    Let me simple explain, when a function created nothing happen until it called so for loop in 1st code called 5 times but not called immediately so when it called i.e after 1 second and also this is asynchronous so before this for loop finished and store value 5 in var i and finally execute setTimeout function five time and print 5,5,5,5,5

Here how it solve using IIFE i.e Immediate Invoking Function Expression

   (function(j){  //i is passed here           
                setTimeout(function(){
                    console.log(j);
                },1000);
            })(i);  //look here it called immediate that is store i=0 for 1st loop, i=1 for 2nd loop, and so on and print 0,1,2,3,4

For more, please understand execution context to understand closure.

- There is one more solution to solve this using let (ES6 feature) but under the hood above function is worked

 for(let i=0; i< 5; i++){           
                setTimeout(function(){
                    console.log(i);
                },1000);                        
            }

Output: 0,1,2,3,4