我时不时地看到“关闭”被提及,我试图查找它,但是Wiki没有给出我理解的解释。有人可以帮我吗?
我时不时地看到“关闭”被提及,我试图查找它,但是Wiki没有给出我理解的解释。有人可以帮我吗?
Answers:
(免责声明:这是一个基本的解释;就定义而言,我正在简化一下)
想到闭包的最简单方法是一个可以存储为变量的函数(称为“第一类函数”),该函数具有特殊的功能,可以访问在其创建的作用域内本地的其他变量。
示例(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是闭包。您可以看到它们都引用了boolean变量,但是该变量是在函数外部分配的。因为对于定义函数的作用域是局部的,所以保留了指向该变量的指针。document.onclick
displayValOfBlack
black
black
如果将其放在HTML页面中:
这说明两者都可以访问相同的 black
,并且可以用于存储状态而无需任何包装对象。
调用此命令setKeyPress
是为了演示如何像任何变量一样传递函数。闭包中保留的范围仍然是定义函数的范围。
闭包通常用作事件处理程序,尤其是在JavaScript和ActionScript中。正确使用闭包将帮助您将变量隐式绑定到事件处理程序,而无需创建对象包装。但是,粗心的使用会导致内存泄漏(例如,当未使用但保留的事件处理程序是唯一保留在内存中的大对象(尤其是DOM对象)上以防止垃圾收集时,)。
1:实际上,JavaScript中的所有函数都是闭包。
black
在函数内部声明了,在栈展开时不会被破坏吗?
black
在函数内声明,不会被销毁” 时您似乎要指的是。还请记住,如果在函数中声明一个对象,然后将其分配给其他位置的变量,则该对象将保留,因为还有其他引用。
闭包基本上只是查看对象的另一种方式。对象是绑定了一个或多个功能的数据。闭包是一种绑定了一个或多个变量的函数。至少在实现级别上,两者基本相同。真正的区别在于它们来自何处。
在面向对象的编程中,您可以通过预先定义其成员变量及其方法(成员函数)来声明对象类,然后创建该类的实例。每个实例都带有成员数据的副本,该副本由构造函数初始化。然后,您将拥有一个对象类型的变量,并将其作为一条数据传递出去,因为重点在于其作为数据的性质。
另一方面,在闭包中,对象不是像对象类那样预先定义的,也不是通过代码中的构造函数调用实例化的。相反,您可以将闭包编写为另一个函数内部的一个函数。闭包可以引用任何外部函数的局部变量,编译器会检测到该变量并将这些变量从外部函数的堆栈空间移至闭包的隐藏对象声明。然后,您将得到一个闭包类型的变量,即使它基本上是一个底层的对象,您也可以将其作为函数引用传递,因为重点在于其作为函数的性质。
术语“ 封闭”来自这样一个事实,即一段代码(块,函数)可以具有自由变量,这些自由变量被定义代码块的环境封闭(即绑定到一个值)。
以Scala函数定义为例:
def addConstant(v: Int): Int = v + k
在函数主体中,有两个名称(变量)v
并k
指示两个整数值。名称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
已经成为一个封闭,因为它的所有自由变量正在关闭(绑定到一个值):addConstant
可以调用,就好像它是一个功能传来传去。请注意,在定义k
闭包时,free变量将绑定到一个值,而在调用闭包时,参数变量将被绑定。v
因此,闭包基本上是一个函数或代码块,可以在上下文绑定了自由变量后通过其自由变量访问非局部值。
在许多语言中,如果只使用一次闭包,则可以将其设为匿名,例如
def increaseAll(values: List[Int]): List[Int] =
{
val k = 2
values.map(v => v + k)
}
请注意,没有自由变量的函数是闭包的特殊情况(具有空的自由变量集)。类似地,匿名函数是匿名闭包的特例,即匿名函数是没有自由变量的匿名闭包。
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
函数将始终能够读取/写入variable的值closure
。
如果运行此代码,则第一次迭代将为closure
变量赋值0 并将函数重写为:
var closure_example = function(){
alertValue : function(){
closure++;
alert(closure);
}
}
并且由于alertValue
需要局部变量closure
来执行功能,因此它将自身与先前分配的局部变量的值绑定在一起closure
。
现在,每次调用该closure_example
函数时,closure
由于alert(closure)
绑定,它将写出变量的增量值。
closure_example.alertValue()//alerts value 1
closure_example.alertValue()//alerts value 2
closure_example.alertValue()//alerts value 3
//etc.
本质上,“封闭”是将某些本地状态和某些代码组合到一个程序包中。通常,局部状态来自周围的(词法)范围,并且代码(本质上)是内部函数,然后返回给外部。然后,闭包是内部函数看到的已捕获变量和内部函数的代码的组合。
不幸的是,由于不熟悉,这是其中一件很难解释的事情。
我过去成功使用的一个比喻是:“想象一下,我们有一个叫做“书”的东西,在封闭的房间里,“那本书”是在TAOCP角落里的副本,但在封闭的桌子上,那是一本德累斯顿文件书的副本。因此,根据您所处的关闭状态,代码“给我书”会导致发生不同的事情。”
static
局部变量的C函数是否可以视为闭包?Haskell的关闭是否涉及国家?
static
局部变量的闭包,而您只有一个)。
如果不定义“状态”的概念,就很难定义闭包是什么。
基本上,在具有完整词法作用域的语言中,将函数视为头等值,会发生一些特殊情况。如果我要执行以下操作:
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%相同。即使有时将它们用于解决类似问题。
我不确定的是,在上面的代码示例中,匿名函数还是高阶函数被视为闭包?
闭包是具有关联状态的函数:
在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");
}
让我们考虑一个简单的函数:
function f1(x) {
// ... something
}
该函数称为顶级函数,因为它没有嵌套在任何其他函数中。每个 JavaScript函数都会将自己的对象列表关联到一个“作用域链”。该作用域链是对象的有序列表。这些对象中的每一个都定义了一些变量。
在顶级函数中,作用域链由单个对象(全局对象)组成。例如,f1
上面的函数有一个范围链,其中有一个定义所有全局变量的对象。(请注意,这里的“对象”一词并不表示JavaScript对象,它只是一个实现定义的对象,它充当变量容器,JavaScript可以在其中“查找”变量。)
调用此函数时,JavaScript创建一个称为“激活对象”的东西,并将其放在作用域链的顶部。该对象包含所有局部变量(例如x
此处)。因此,现在我们在范围链中有两个对象:第一个是激活对象,而在其下方是全局对象。
请非常小心地注意,这两个对象在不同的时间被放入作用域链中。定义函数时(即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,这是David Flanagan的权威指南
大多数函数是使用定义该函数时生效的作用域链来调用的,并且涉及闭包并不重要。当闭包在不同于定义时生效的作用域链下被调用时,它们变得很有趣。从定义嵌套函数的函数返回嵌套函数对象时,这种情况最常见。
函数返回时,该激活对象将从作用域链中删除。如果没有嵌套函数,则不会再有对激活对象的引用,它会被垃圾回收。如果定义了嵌套函数,则这些函数中的每一个都有对作用域链的引用,而该作用域链则指向激活对象。
但是,如果这些嵌套函数对象保留在其外部函数中,则它们本身将与所引用的激活对象一起被垃圾回收。但是,如果该函数定义了一个嵌套函数并将其返回或将其存储到某个位置的属性中,则将有对该嵌套函数的外部引用。它不会被垃圾收集,它所指向的激活对象也不会被垃圾收集。
在上面的示例中,我们没有f2
从中返回f1
,因此,当调用return 时f1
,其激活对象将从其作用域链中删除并进行垃圾回收。但是,如果我们有这样的事情:
function f1(x) {
function f2(y) {
// ... something
}
return f2;
}
在这里,返回f2
将具有范围链,该范围链将包含的激活对象f1
,因此不会被垃圾回收。此时,如果我们调用f2
,即使我们不在,它也将能够访问f1
的变量。x
f1
因此,我们可以看到一个函数保持了它的作用域链,并且作用域链伴随着外部函数的所有激活对象。这是关闭的本质。我们说JavaScript中的函数是“词法范围”的,这意味着它们保存了定义时处于活动状态的范围,而不是调用它们时处于活动状态的范围。
有许多强大的编程技术都涉及到闭包,例如近似私有变量,事件驱动的编程,部分应用程序等。
还要注意,所有这些都适用于所有支持闭包的语言。例如PHP(5.3 +),Python,Ruby等。
闭包是编译器优化(又名语法糖?)。有些人也将其称为“ 穷人的对象 ”。
请参阅Eric Lippert的答案:(以下摘录)
编译器将生成如下代码:
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都以几乎相同的方式创建了闭包。