我时不时地看到“关闭”被提及,我试图查找它,但是Wiki没有给出我理解的解释。有人可以帮我吗?

评论

如果您知道Java / C#,希望此链接对您有所帮助-http://www.developerfusion.com/article/8251/the-beauty-of-closures/

关闭很难理解。您应该尝试单击该Wikipedia文章的第一句中的所有链接,并首先理解这些文章。

stackoverflow.com/questions/36636/what-is-a-closure

但是,闭包和类之间的根本区别是什么?好的,只有一个公共方法的类。

@biziclop:您可以使用类来模拟闭包(这是Java开发人员必须要做的)。但是创建它们通常比较繁琐,您不必手动管理自己正在处理的内容。 (顽固的lispers提出了类似的问题,但很可能得出另一个结论-当您有闭包时,不需要语言级别的OO支持。

#1 楼

(免责声明:这是一个基本的解释;就定义而言,我正在简化一下)变量(称为“一流函数”),具有访问在其创建范围内本地的其他变量的特殊功能。

示例(JavaScript):

 var setKeyPress = function(callback) {
    document.onkeypress = callback;
};

var initialize = function() {
    var black = false;

    document.onclick = function() {
        black = !black;
        document.body.style.backgroundColor = black ? "#000000" : "transparent";
    }

    var displayValOfBlack = function() {
        alert(black);
    }

    setKeyPress(displayValOfBlack);
};

initialize();
 


分配给document.onclickdisplayValOfBlack的function1是闭包。您可以看到它们都引用了布尔变量black,但是该变量是在函数外部分配的。因为black在定义函数的作用域内是本地的,所以保留了指向该变量的指针。

如果将其放在HTML页面中:单击更改为黑色
单击[输入]以查看“真”
再次单击,更改为白色
单击[输入]以查看为“假”

这说明两者都可以访问相同的black,并且可以在没有任何包装对象的情况下用于存储状态。闭包中保留的范围仍然是定义函数的范围。

闭包通常用作事件处理程序,尤其是在JavaScript和ActionScript中。正确使用闭包将帮助您将变量隐式绑定到事件处理程序,而无需创建对象包装。但是,粗心的使用会导致内存泄漏(例如,当未使用但保留的事件处理程序是唯一保留在内存中的大对象(尤其是DOM对象)上时,这会阻止垃圾回收。) br /> 1:实际上,JavaScript中的所有函数都是闭包。

评论


在阅读您的答案时,我的脑海里闪过一个灯泡。非常感激! :)

–杰伊
2011年1月27日23:30

由于在函数内部声明了black,当堆栈展开时,它不会被破坏吗?

–加布林
11年1月29日在9:32

@gablin,这就是具有闭包的语言的独特之处。具有垃圾回收的所有语言的工作方式几乎相同-当不再保留对某个对象的引用时,可以将其销毁。每当在JS中创建函数时,本地作用域都会绑定到该函数,直到该函数被销毁为止。

–妮可
2011年1月29日在18:18

@gablin, that's a good question. I don't think they can't — but I only brought up garbage collection since that what JS uses and that's what you seemed to be referring to when you said "Since black is declared inside a function, wouldn't that get destroyed". Remember also that if you declare an object in a function and then assign it to a variable that lives on somewhere else, that object is preserved because there are other references to it.

– Nicole
Jan 30 '11 at 17:25



Objective-C(和Clang下的C)支持块,这些块本质上是闭包,没有垃圾回收。它需要运行时支持和一些有关内存管理的手动干预。

– Ben Zotto
2012年11月6日18:19

#2 楼

闭包基本上只是查看对象的另一种方式。对象是绑定了一个或多个功能的数据。闭包是一种绑定了一个或多个变量的函数。两者至少在实现级别上基本相同。真正的区别在于它们的来源。

在面向对象的编程中,您通过预先定义其成员变量及其方法(成员函数)来声明对象类,然后创建实例该类的。每个实例都带有成员数据的副本,该副本由构造函数初始化。然后,您将获得一个对象类型的变量,并将其作为数据传递给它,因为重点在于它作为数据的性质。而不是像对象类一样预先定义,或通过代码中的构造函数调用实例化。相反,您将闭包编写为另一个函数内部的一个函数。闭包可以引用任何外部函数的局部变量,并且编译器会检测到该变量,并将这些变量从外部函数的堆栈空间移至闭包的隐藏对象声明。然后,您将获得一个闭包类型的变量,即使它基本上是一个底层对象,您也可以将其作为函数引用传递,因为重点在于其作为函数的性质。

评论


+1:好答案。您可以将闭包视为仅使用一种方法的对象,将任意对象视为对某些常见基础数据(对象的成员变量)的闭包的集合。我认为这两种观点是相当对称的。

–乔治
2012年11月6日,11:03

很好的答案。它实际上解释了关闭的见解。

–RoboAlex
2013年9月8日13:34

@Mason Wheeler:闭包数据存储在哪里?像函数一样在堆栈中?还是像对象一样堆放?

–RoboAlex
2013年9月8日13:37

@RoboAlex:在堆中,因为它是一个看起来像函数的对象。

–梅森·惠勒
2013年9月8日14:36

@RoboAlex:闭包及其捕获的数据的存储位置取决于实现。在C ++中,它可以存储在堆中或堆栈中。

–乔治
15年6月15日在20:54

#3 楼

闭包这个术语来自这样一个事实,即一段代码(块,函数)可以具有自由变量,这些自由变量在定义代码块的环境中被封闭(即绑定到一个值)。

以Scala函数定义为例:

def addConstant(v: Int): Int = v + k


在函数主体中,有两个名称(变量)vk表示两个整数值。名称v被绑定是因为它被声明为函数addConstant的自变量(通过查看函数声明,我们知道在调用函数时v将被分配一个值)。名称k与函数addConstant一起使用是免费的,因为该函数不包含关于k绑定到哪个值(以及绑定到哪个值)的任何线索。 />
val n = addConstant(10)


我们必须给k分配一个值,只有在定义k的上下文中定义了名字addConstant时,这种情况才会发生。例如:

def increaseAll(values: List[Int]): List[Int] =
{
  val k = 2

  def addConstant(v: Int): Int = v + k

  values.map(addConstant)
}


现在,我们已经在定义addConstant的上下文中定义了kaddConstant已成为闭包,因为它的所有自由变量现已关闭(绑定一个值):addConstant可以被调用和传递,就好像它是一个函数一样。请注意,在定义闭包时将自由变量k绑定到一个值,而在调用闭包时将参数变量v绑定到一个。在上下文中绑定了非局部值之后,可以通过其自由变量访问非局部值。

在许多语言中,如果仅使用一次闭包,则可以将其设为匿名,例如/>
def increaseAll(values: List[Int]): List[Int] =
{
  val k = 2

  values.map(v => v + k)
}


请注意,没有自由变量的函数是闭包的特殊情况(具有空的自由变量集)。类似地,匿名函数是匿名闭包的特例,即匿名函数是没有自由变量的匿名闭包。

评论


这与逻辑上的封闭式和开放式公式格格不入。感谢您的回答。

– RainDoctor
2014-09-23 8:46

@RainDoctor:自由变量在逻辑公式和lambda演算表达式中的定义方式相似:lambda表达式中的lambda就像逻辑公式中带有自由/绑定变量的量词一样。

–乔治
2014-09-23 10:45

#4 楼

JavaScript中的一个简单说明:

var closure_example = function() {
    var closure = 0;
    // after first iteration the value will not be erased from the memory
    // because it is bound with the returned alertValue function.
    return {
        alertValue : function() {
            closure++;
            alert(closure);
        }
    };
};
closure_example();


alert(closure)将使用先前创建的closure值。返回的alertValue函数的名称空间将连接到closure变量所在的名称空间。删除整个函数时,将删除closure变量的值,但在此之前,alertValue函数将始终能够读取/写入变量closure的值。

如果运行此函数代码,第一次迭代将为closure变量分配一个值0,并将函数重写为:它会与先前分配的局部变量alertValue的值绑定在一起。 >
var closure_example = function(){
    alertValue : function(){
        closure++;
        alert(closure);
    }       
}


评论


谢谢,我没有测试代码=)现在一切似乎都很好。

–穆哈
11年1月28日在21:12

#5 楼

本质上,“封闭”是将某些局部状态和某些代码组合到一个程序包中。通常,局部状态来自周围的(词法)范围,并且代码(本质上)是内部函数,然后返回给外部。然后,闭包是内部函数看到的已捕获变量与内部函数的代码的组合。

不幸的是,由于其中之一,这些事情之一很难解释。

我过去成功使用的一个比喻是:“想象一下,我们有一个叫做“书”的东西,在房间里,“书”就是那儿在角落里的副本。是TAOCP的,但在表的关闭位置上,这是一本德累斯顿文件书的副本。因此,根据您所处的关闭位置,代码“给我该书”会导致发生不同的事情。“

评论


您忘记了这一点:答案中的en.wikipedia.org/wiki/Closure_(computer_programming)。

– S.Lott
2011年1月27日,11:14

不,我有意识地选择不关闭该页面。

–疫苗
2011-1-27的15:11

“状态和函数。”:具有静态局部变量的C函数是否可以视为闭包? Haskell的关闭是否涉及国家?

–乔治
2012年11月6日20:43



Haskell中的@Giorgio闭包确实(我相信)在定义它们的词法范围内的论点附近,因此,我会说“是”(尽管我最多不熟悉Haskell)。带有静态变量的C函数充其量是一个非常有限的闭包(您确实希望能够使用静态局部变量从一个函数创建多个闭包,而您只有一个)。

–疫苗
2012年11月7日在8:54

我故意问这个问题是因为我认为带有静态变量的C函数不是闭包:静态变量是在本地定义的,并且仅在闭包内部才知道,它无法访问环境。另外,我不确定100%,但是我会反过来制定您的声明:您使用闭包机制创建不同的函数(一个函数是闭包定义+其自由变量的绑定)。

–乔治
2012年11月7日上午10:13

#6 楼

如果不定义“状态”的概念,就很难定义闭包。如果我要执行以下操作:

function foo(x)
return x
end

x = foo


变量x不仅引用function foo(),而且还引用了上次返回时foo保留的状态。当foo在其范围内进一步定义其他功能时,真正的魔力就会发生。它就像自己的迷你环境(就像我们通常在全局环境中定义函数一样)。

从功能上讲,它可以解决许多与C ++(C?)的“静态”问题相同的问题。 '关键字,它在多个函数调用中保留局部变量的状态;但是,这更像是将相同的原理(静态变量)应用于函数,因为函数是一等值。闭包增加了对要保存的整个函数状态的支持(与C ++的静态函数无关)。

将函数作为头等值处理并添加对闭包的支持还意味着您可以拥有多个实例具有相同功能的内存(类似于类)。这意味着您可以重用相同的代码,而不必重置函数的状态,这在处理函数内部的C ++静态变量时是必需的(这可能是错误的?)是Lua的闭包支持的一些测试。

--Closure testing
--By Trae Barlow
--

function myclosure()
    print(pvalue)--nil
    local pvalue = pvalue or 10
    return function()
        pvalue = pvalue + 10 --20, 31, 42, 53(53 never printed)
        print(pvalue)
        pvalue = pvalue + 1 --21, 32, 43(pvalue state saved through multiple calls)
        return pvalue
    end
end

x = myclosure() --x now references anonymous function inside myclosure()

x()--nil, 20
x() --21, 31
x() --32, 42
    --43, 53 -- if we iterated x() again



nil
20
31
42


它可能会变得很棘手,并且可能因语言而异,但是在Lua中似乎每当执行一个函数时,其状态都会被重置。我之所以这样说是因为,如果我们直接访问myclosure函数/状态(而不是通过返回的匿名函数),则上述代码的结果将有所不同,因为pvalue将被重置为10;但是,如果我们通过x(匿名函数)访问myclosure的状态,您会看到pvalue仍然存在并且在内存中的某个位置。我怀疑还有更多的东西,也许有人可以更好地解释实现的本质。 ),因此请注意,这不是C ++ 11和Lua中的闭包之间的比较。同样,从Lua到C ++的所有“界线”都是相似的,因为静态变量和闭包不是100%相同。即使有时有时使用它们来解决类似问题。

我不确定的是,在上面的代码示例中,匿名函数还是高阶函数被视为闭包?

#7 楼

闭包是具有关联状态的函数:

在perl中,您可以这样创建闭包:

#!/usr/bin/perl

# This function creates a closure.
sub getHelloPrint
{
    # Bind state for the function we are returning.
    my ($first) = @_;a

    # The function returned will have access to the variable $first
    return sub { my ($second) = @_; print  "$first $second\n"; };
}

my $hw = getHelloPrint("Hello");
my $gw = getHelloPrint("Goodby");

&$hw("World"); // Print Hello World
&$gw("World"); // PRint Goodby World


如果我们看一下新功能C ++提供。
它还允许您将当前状态绑定到对象:

#include <string>
#include <iostream>
#include <functional>


std::function<void(std::string const&)> getLambda(std::string const& first)
{
    // Here we bind `first` to the function
    // The second parameter will be passed when we call the function
    return [first](std::string const& second) -> void
    {   std::cout << first << " " << second << "\n";
    };
}

int main(int argc, char* argv[])
{
    auto hw = getLambda("Hello");
    auto gw = getLambda("GoodBye");

    hw("World");
    gw("World");
}


#8 楼

让我们考虑一个简单的函数:

function f1(x) {
    // ... something
}


此函数被称为顶级函数,因为它没有嵌套在任何其他函数中。每个JavaScript函数都会将自己的对象列表关联到一个“作用域链”。该作用域链是对象的有序列表。每个对象都定义一些变量。

在顶级函数中,作用域链由单个对象(全局对象)组成。例如,上面的函数f1具有作用域链,该作用域链中包含一个定义所有全局变量的对象。 (请注意,这里的“对象”一词并不表示JavaScript对象,它只是一个实现定义的对象,它充当变量容器,JavaScript可以在其​​中“查找”变量。)调用后,JavaScript创建一个称为“激活对象”的东西,并将其放在作用域链的顶部。该对象包含所有局部变量(例如,此处为x)。因此,现在我们在作用域链中有两个对象:第一个是激活对象,在其下方是全局对象。

请非常小心地注意,这两个对象在不同的​​时间被放入作用域链中。在定义函数时(即JavaScript解析函数并创建函数对象时)将放置全局对象,而在调用函数时将进入激活对象。

因此,我们现在知道了:


每个函数都有一个与其关联的作用域链
定义该函数(创建函数对象时)后,JavaScript会使用该函数保存一个作用域链
/>对于顶级函数,作用域链在函数定义时仅包含全局对象,并在调用时在顶部添加一个额外的激活对象。

当我们处理嵌套函数时,情况变得很有趣。因此,让我们创建一个:

function f1(x) {

    function f2(y) {
        // ... something
    }

}


当定义了f1时,我们得到了一个仅包含全局对象的作用域链。该激活对象包含变量f1和作为函数的变量f1。并且,请注意,正在定义x。因此,此时,JavaScript还为f2保存了新的作用域链。为此内部功能保存的作用域链是当前有效的作用域链。当前有效的作用域链是f2的作用域链。因此,f2的作用域链是f1的当前作用域链-包含f2的激活对象和全局对象。

调用f1时,它得到的是自己的包含f1的激活对象,已添加到它的作用域链中,该作用域链已经包含f2的激活对象和全局对象。两个外部函数和一个全局对象),以及在调用时添加4个。

因此,现在我们了解了作用域链的工作原理,但是我们还没有讨论闭包。


函数对象和作用域变量(一组变量绑定)在其中解析函数的变量的组合在计算机科学文献中称为闭包-JavaScript,这是David Flanagan的权威指南


大多数功能是使用sa调用的定义函数时有效的作用域链,涉及闭包并不重要。在不同于定义时生效的作用域链下调用闭包时,它变得很有趣。当嵌套的函数对象从其定义的函数中返回时,这最常见。

函数返回时,该激活对象将从作用域链中删除。如果没有嵌套函数,则不会再有对激活对象的引用,它会被垃圾回收。如果定义了嵌套函数,则每个函数都有对作用域链的引用,而该作用域链则指向激活对象。 ,则它们本身将与所引用的激活对象一起被垃圾回收。但是,如果该函数定义了一个嵌套函数并将其返回或将其存储到某个位置的属性中,则将有对该嵌套函数的外部引用。

在上面的示例中,我们不会从y返回f1,因此,在调用时到f2返回时,将从其作用域链中删除其激活对象并收集垃圾。但是,如果我们有这样的内容:

function f1(x) {

    function f2(y) {
        // ... something
    }

    return f2;
}


此处,返回的f2将具有一个范围链,该范围链将包含f1的激活对象,因此不会被垃圾收集。此时,如果我们调用f1,即使我们不在f2内,也可以访问f1的变量f2。因此,我们可以看到函数将其保持在作用域链中范围链伴随着外部功能的所有激活对象。这是关闭的本质。我们说JavaScript中的函数是“词法范围”的,这意味着它们保存了定义时有效的范围,而不是调用它们时有效的范围。

有很多数字强大的编程技术,涉及诸如近似私有变量,事件驱动的编程,部分应用程序之类的闭包。

还要注意,所有这些都适用于所有支持闭包的语言。例如PHP(5.3 +),Python,Ruby等。

#9 楼

闭包是编译器的优化(又名语法糖?)。某些人也将其称为“穷人对象”。

请参见Eric Lippert的回答:(摘录如下) br />
 private class Locals
{
  public int count;
  public void Anonymous()
  {
    this.count++;
  }
}

public Action Counter()
{
  Locals locals = new Locals();
  locals.count = 0;
  Action counter = new Action(locals.Anonymous);
  return counter;
}
 


有道理吗?
您还要求进行比较。 VB和JScript都以几乎相同的方式创建闭包。

评论


这个答案是CW,因为我不配Eric的出色答案。请根据需要对其进行投票。高温超导

–半比特
2011年1月27日22:43

-1:您的解释太扎根于C#。闭包在许多语言中都被使用,并且比这些语言中的语法糖要多得多,并包含功能和状态。

–马丁·约克
2011年1月28日在21:09

不,闭包不仅是“编译器优化”,也不是语法糖。 -1

–user39685
2012年11月6日12:43