it-swarm.cn

什么是封包?

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

157
gablin

(免责声明:这是一个基本解释;就定义而言,我正在简化一下)

想到闭包的最简单方法是可以存储为变量的函数(称为“一流函数”),它具有访问本地其他变量的特殊能力。它在其中创建的范围。

示例(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();

功能1个 分配给 document.onclickdisplayValOfBlack是闭包。您可以看到它们都引用了布尔变量black,但是该变量是在函数外部分配的。因为black局部于函数定义的范围,所以保留了指向该变量的指针。

如果将其放在HTML页面中:

  1. 单击更改为黑色
  2. 点击[输入]以查看“真”
  3. 再次单击,变回白色
  4. 点击[输入]以查看“假”

这表明两者都可以访问sameblack,并且可以用来存储状态没有任何包装对象。

setKeyPress的调用是为了演示如何像任何变量一样传递函数。保留在闭包中的scope仍然是定义函数的地方。

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


1:实际上,JavaScript中的所有函数都是闭包。

141
Nicole

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

在面向对象的编程中,您通过预先定义其成员变量及其方法(成员函数)来声明一个对象类,然后创建该类的实例。每个实例都带有成员数据的副本,该副本由构造函数初始化。然后,您将拥有一个对象类型的变量,并将其作为一条数据传递出去,因为重点在于其作为数据的性质。

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

69
Mason Wheeler

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

以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的上下文中定义了k,则addConstant已成为闭包,因为现在,其所有自由变量都被closed(绑定到一个值):addConstant可以被调用并传递,就好像它是一个函数一样。请注意,当闭包被定义时,自由变量k被绑定到一个值,而参数变量v被绑定当调用了闭包时。

因此,闭包基本上是一个函数或代码块,可以在上下文绑定了非局部值之后通过其自由变量访问非局部值。

在许多语言中,如果只使用一次闭包,则可以使它成为匿名,例如.

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

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

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

29
Giorgio

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并将函数重写为:

_var closure_example = function(){
    alertValue : function(){
        closure++;
        alert(closure);
    }       
}
_

并且由于alertValue需要局部变量closure才能执行该功能,因此它将自身与先前分配的局部变量closure的值绑定在一起。

现在,每次调用_closure_example_函数时,由于alert(closure)已绑定,它将写出closure变量的增量值。

_closure_example.alertValue()//alerts value 1 
closure_example.alertValue()//alerts value 2 
closure_example.alertValue()//alerts value 3
//etc. 
_
9
Muha

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

不幸的是,由于不熟悉,这是其中一件难以解释的事情。

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

5
Vatine

如果不定义“状态”的概念,很难定义闭包是什么。

基本上,在具有完整词法作用域的语言中,将函数视为头等值,会发生一些特殊情况。如果我要执行以下操作:

_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仍然存在并且在内存中的某个位置。我怀疑还有更多的东西,也许有人可以更好地解释实现的本质。

PS:我不了解C++ 11(不是以前版本中的东西),因此请注意,这不是C++ 11和Lua中的闭包之间的比较。同样,从Lua到C++的所有“界线”都是相似的,因为静态变量和闭包不是100%相同。即使有时将它们用于解决类似问题。

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

5
Trae Barlow

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

在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");
}
4
Martin York

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

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

该函数称为顶级函数,因为它没有嵌套在任何其他函数中。 每个JavaScript函数将自身与称为“范围链”。该作用域链是对象的有序列表。这些对象中的每一个都定义了一些变量。

在顶级函数中,作用域链由单个对象(全局对象)组成。例如,上面的函数f1具有作用域链,该作用域链中具有一个定义所有全局变量的对象。 (请注意,这里的“对象”一词并不表示JavaScript对象,它只是一个实现定义的对象,它充当变量容器,JavaScript可以在其​​中“查找”变量。)

调用此函数时,JavaScript创建一个称为“激活对象”的东西,并将其放在作用域链的顶部。该对象包含所有局部变量(例如x)。因此,现在我们在范围链中有两个对象:第一个是激活对象,而在其下方是全局对象。

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

因此,我们现在知道这一点:

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

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

function f1(x) {

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

}

定义f1时,我们将获得一个仅包含全局对象的作用域链。

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

调用f2时,将获得包含y的自己的激活对象,并将其添加到其作用域链中,该作用域链已包含f1的激活对象和全局对象。

如果在f2中定义了另一个嵌套函数,则它的作用域链在定义时将包含三个对象(两个外部函数的2个激活对象和全局对象),在调用时包含4个对象。

因此,现在我们了解范围链是如何工作的,但是我们还没有谈论闭包。

函数对象和范围(一组变量绑定)之间的组合(在该范围内解析函数的变量)在计算机科学文献中称为闭包 JavaScript通过大卫·弗拉纳根

大多数函数是使用定义该函数时有效的作用域链来调用的,并且涉及闭包并不重要。当闭包在不同于定义时生效的作用域链下被调用时,它们变得很有趣。当嵌套函数对象从定义它的函数中返回返回时,通常会发生这种情况。

函数返回时,该激活对象将从作用域链中删除。如果没有嵌套函数,则不会再有对激活对象的引用,它会被垃圾回收。如果定义了嵌套函数,则这些函数中的每一个都有对作用域链的引用,而该作用域链则指向激活对象。

但是,如果这些嵌套函数对象保留在其外部函数中,那么它们本身将与引用的激活对象一起被垃圾回收。但是,如果该函数定义了一个嵌套函数并将其返回或将其存储到某个位置的属性中,则将有对该嵌套函数的外部引用。它不会被垃圾收集,它所引用的激活对象也不会被垃圾收集。

在上面的示例中,我们没有从f2返回f1,因此,当对f1的调用返回时,其激活对象将从其作用域链中删除并进行垃圾回收。但是,如果我们有这样的事情:

function f1(x) {

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

    return f2;
}

在这里,返回的f2将具有作用域链,该作用域链将包含f1的激活对象,因此不会被垃圾回收。此时,如果我们调用f2,则即使我们不在f1内,它也将能够访问f1的变量x

因此,我们可以看到一个函数保持了它的作用域链,并且作用域链伴随着外部函数的所有激活对象。这是关闭的本质。我们说JavaScript中的函数是“按词法作用域”,这意味着它们保存了定义时处于活动状态的作用域,而不是他们接到电话时很活跃。

有许多强大的编程技术都涉及到闭包,例如近似私有变量,事件驱动的编程, 部分应用程序 等。

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

2
treecoder