什么是“closures”?

我问了一个关于柯里和closures的问题。 什么是封闭? 它如何涉及到咖喱?

可变范围

当你声明一个局部variables时,该variables有一个作用域。 通常局部variables只存在于声明它们的块或函数中。

function() { var a = 1; console.log(a); // works } console.log(a); // fails 

如果我尝试访问本地variables,大多数语言将在当前范围内查找它,然后遍历父范围,直到它们到达根范围。

 var a = 1; function() { console.log(a); // works } console.log(a); // works 

当一个块或函数完成后,其局部variables不再需要,通常会被内存溢出。

这是我们通常期待的事情。

闭包是一个持久的局部variables作用域

闭包是一个持久的作用域,即使在代码执行已经移出该块之后,该作用域仍然保持局部variables。 支持闭包的语言(如JavaScript,Swift和Ruby)将允许您保持对作用域(包括其父作用域)的引用,即使在声明这些variables的块已经完成执行之后,只要您保留引用该块或function的地方。

范围对象及其所有局部variables与函数绑定在一起,只要该函数持续存在,该对象将一直存在。

这给了我们function的可移植性。 我们可以期望当函数被第一次定义时,任何在后面调用该函数的范围内的variables仍然在范围内,即使我们在完全不同的上下文中调用该函数。

例如

这里有一个非常简单的JavaScript例子来说明这一点:

 outer = function() { var a = 1; var inner = function() { console.log(a); } return inner; // this returns a function } var fnc = outer(); // execute outer to get inner fnc(); 

这里我定义了一个函数内的函数。 内部函数可以访问所有外部函数的局部variables,包括a 。 variablesa在内部函数的范围内。

通常当一个函数退出时,所有的局部variables都被吹走。 但是,如果我们返回内部函数并将其赋值给variablesfnc ,以便在outer函数退出后它仍然存在,那么在定义inner函数时所有在范围inner的variables也将保留 。 variablesa已经被closures了 – 它在closures中。

请注意,variablesafnc完全私有的。 这是一种在JavaScript等函数式编程语言中创build私有variables的方法。

正如你可能猜到的那样,当我调用fnc()它会打印a “1”的值。

在没有闭包的语言中,当函数outer退出时,variablesa将被垃圾收集并抛出。 调用fnc会抛出一个错误,因为不再存在。

在JavaScript中,variablesa持续存在,因为函数首次被声明时会创buildvariables作用域,只要函数继续存在,该variables就会一直存在。

属于outer的范围。 inner的范围有一个指向outer范围的父指针。 fnc是一个指向inner的variables。 只要fnc依然存在,一直存在。 a是在closures内。

我将举一个例子(在JavaScript中):

 function makeCounter () { var count = 0; return function () { count += 1; return count; } } var x = makeCounter(); x(); returns 1 x(); returns 2 ...etc... 

makeCounter这个函数做了什么,它返回一个我们称之为x的函数,每次函数调用一个函数。 由于我们没有提供任何参数给x,所以它必须记得计数。 它知道在哪里可以find它的基础上,所谓的词汇范围 – 它必须看它的位置,以find它的价值。 这个“隐藏的”值就是所谓的闭包。

这是我的又一个例子:

 function add (a) { return function (b) { return a + b; } } var add3 = add(3); add3(4); returns 7 

你可以看到,当你用参数a(它是3)调用add的时候,那个值被包含在我们定义为add3的返回函数的闭包中。 这样,当我们调用add3时,它知道在哪里find一个值来执行加法。

凯尔的答案相当不错。 我认为唯一另外的解释是闭包基本上是在创buildlambda函数的时候栈的快照。 然后,当函数被重新执行时,在执行该函数之前,堆栈被恢复到该状态。 因此,正如Kyle提到的,当lambda函数执行时,隐藏的值( count )是可用的。

闭包是一个可以在另一个函数中引用状态的函数。 例如,在Python中,这使用闭包“内部”:

 def outer (a): b = "variable in outer()" def inner (c): print a, b, c return inner # Now the return value from outer() can be saved for later func = outer ("test") func (1) # prints "test variable in outer() 1 

为了帮助理解封锁,研究如何以程序语言来实施它们可能是有用的。 这个解释将遵循Scheme的简单实施。

首先,我必须介绍命名空间的概念。 当您向Scheme解释器input命令时,它必须评估expression式中的各种符号并获取其值。 例:

 (define x 3) (define y 4) (+ xy) returns 7 

定义expression式将值3存储在x的点中,将值4存储在y的点中。 然后当我们调用(+ xy)的时候,解释器会查找名字空间中的值并且能够执行操作并返回7。

然而,在Scheme中有一些expression式允许你暂时覆盖一个符号的值。 这是一个例子:

 (define x 3) (define y 4) (let ((x 5)) (+ xy)) returns 9 x returns 3 

let关键字的作用是引入一个新的名称空间,其中x的值为5.您将注意到它仍然能够看到y为4,使得返回的和为9.您还可以看到一旦expression式结束x从这个意义上来说,x已经被当地价值暂时掩盖了。

程序和面向对象的语言也有类似的概念。 每当你在一个和全局variables同名的函数中声明一个variables时,你会得到同样的效果。

我们将如何执行这个? 一个简单的方法是链接列表 – 头部包含新的值,尾部包含旧的名称空间。 当你需要查找一个符号时,你从头开始,沿着尾巴走。

现在让我们跳到目前实现的一streamfunction。 或多或less,一个函数是一组指令,当函数被调用返回值时结束。 当我们读取函数时,我们可以将这些指令存储在幕后,并在函数调用时运行它们。

 (define x 3) (define (plus-x y) (+ xy)) (let ((x 5)) (plus-x 4)) returns ? 

我们定义x为3,加x为其参数,y加上x的值。 最后,我们在x被一个新的x所掩盖的环境中调用plus-x,这个值为5.如果我们仅仅为函数plus-x存储操作(+ xy),由于我们处于上下文x是5,返回的结果是9.这就是所谓的dynamic范围。

然而,Scheme,Common Lisp和其他许多语言都具有所谓的词法范围 – 除了存储操作(+ xy)之外,我们还将命名空间存储在特定的点上。 这样,当我们查找值时,我们可以看到在这个上下文中x是真的3.这是一个闭包。

 (define x 3) (define (plus-x y) (+ xy)) (let ((x 5)) (plus-x 4)) returns 7 

总之,我们可以使用链表来存储函数定义时的命名空间的状态,从而允许我们从封闭的范围访问variables,同时也能够在不影响其余部分的情况下在本地掩蔽一个variables程序。

首先,与这里大多数人告诉你的是, 封闭不是一个function ! 那什么?
它是在一个函数的“周围环境”(称为它的环境 )中定义的一符号,它使它成为一个CLOSEDexpression式(即每个符号被定义并具有值的expression式,因此可以被评估)。

例如,当你有一个JavaScript函数:

 function closed(x) { return x + 3; } 

它是一个封闭的expression式,因为它中出现的所有符号都被定义在其中(它们的含义是清楚的),所以你可以评估它。 换句话说,它是独立的

但是,如果你有这样的function:

 function open(x) { return x*y + 3; } 

这是一个公开的expression方式,因为里面有没有定义的符号。 也就是说, 在看这个函数的时候,我们不知道y是什么,它是什么意思,我们不知道它的价值,所以我们不能评估这个expression式。 也就是说,我们不能调用这个函数,直到我们告诉它是什么意思。 这个y被称为自由variables

这个乞求一个定义,但是这个定义不是function的一部分,而是定义在其他地方的“周围环境”(也称为环境 )中。 至less这就是我们所希望的:P

例如,它可以在全球范围内定义:

 var y = 7; function open(x) { return x*y + 3; } 

或者可以在包装它的函数中定义它:

 var global = 2; function wrapper(y) { var w = "unused"; return function(x) { return x*y + 3; } } 

给expression式中的自由variables赋予环境的部分是闭包 。 这样调用,因为它通过为所有自由variables提供这些缺失的定义,将一个开放的expression式变成一个闭合的expression式,以便我们可以对它进行评估。

在上面的例子中,内部函数(我们没有给出名称,因为我们不需要它)是一个开放expression式,因为它中的variablesy自由的 – 它的定义在函数之外,在函数中包装它。 这个匿名函数的环境是一组variables:

 { global: 2, w: "unused", y: [whatever has been passed to that wrapper function as its parameter `y`] } 

现在, 闭包就是这个环境的一部分,它通过提供所有自由variables的定义来closures内部函数。 在我们的例子中,内部函数中唯一的自由variables是y ,所以函数的闭包就是它的环境子集:

 { y: [whatever has been passed to that wrapper function as its parameter `y`] } 

在环境中定义的另外两个符号不是该函数closures的一部分,因为它不需要它们运行。 他们不需要closures它。

更多的理论背后,在这里: https : //stackoverflow.com/a/36878651/434562

值得注意的是,在上面的例子中,包装函数返回它的内部函数作为一个值。 从函数定义(或创build)的那一刻起,我们调用这个函数的时间可以是远程的。 特别是,它的包装函数不再运行,并且它在调用堆栈上的参数已经不存在了:P这就产生了一个问题,因为内部函数在调用的时候需要存在。 换句话说,它需要闭包的variables以某种方式超过包装函数,并在需要的时候到达那里。 因此,内部函数必须对这些variables进行快照 ,使其closures,并将其存储在某个安全的地方供以后使用。 (在调用堆栈之外的某处)。

这就是为什么人们经常把closures这个术语混淆成为特殊types的函数,它可以对所使用的外部variables进行这样的快照,或者用于稍后存储这些variables的数据结构。 但是我希望你现在明白,它们本身不是闭包 – 它们只是用编程语言来实现闭包的方法,或者是在需要时允许函数闭包的variables存在的语言机制。 关于闭包有很多误解,这(不必要地)使这个问题更加令人困惑和复杂,实际上是这样。

这里有一个真实世界的例子,为什么closures踢屁股…这是直接从我的Javascript代码。 让我来说明一下。

 Function.prototype.delay = function(ms /*[, arg...]*/) { var fn = this, args = Array.prototype.slice.call(arguments, 1); return window.setTimeout(function() { return fn.apply(fn, args); }, ms); }; 

以下是你将如何使用它:

 var startPlayback = function(track) { Player.play(track); }; startPlayback(someTrack); 

现在想象你想让播放延迟启动,比如在这个代码片段运行5秒之后。 那么这很容易delay ,它closures:

 startPlayback.delay(5000, someTrack); // Keep going, do other things 

当你用5000毫秒调用delay时,第一个代码片段将运行,并将传入的参数存储在它的闭包中。 然后5秒钟后,当setTimeoutcallback发生时,闭包仍然保持这些variables,所以它可以用原始参数调用原始函数。
这是一种咖啡,或function装饰。

没有closures,你将不得不以某种方式维护这些variables在函数之外的状态,从而在逻辑上属于它内部的东西之外乱抛垃圾代码。 使用闭包可以大大提高代码的质量和可读性。

在正常情况下,variables受到范围规则的约束:局部variables仅在定义的函数内起作用。 closures是为了方便暂时打破这个规则的一种方法。

 def n_times(a_thing) return lambda{|n| a_thing * n} end 

在上面的代码中, lambda(|n| a_thing * n}是closures的,因为a_thing是由lambda(匿名函数创build者)引用的。

现在,如果你把得到的匿名函数放在一个函数variables中。

 foo = n_times(4) 

foo会打破正常的范围规则,并在内部开始使用4。

 foo.call(3) 

返回12。

不包含自由variables的函数被称为纯函数。

包含一个或多个自由variables的函数称为闭包。

 var pure = function pure(x){ return x // only own environment is used } var foo = "bar" var closure = function closure(){ return foo // foo is a free variable from the outer environment } 

src: https : //leanpub.com/javascriptallongesix/read#leanpub-auto-if-functions-without-free-variables-are-pure-are-closures-impure

简而言之,函数指针只是指向程序代码库(如程序计数器)中某个位置的指针。 而Closure =函数指针+堆栈帧

TL;博士

闭包是一个函数,它的范围被分配给(或用作)一个variables。 因此,名称闭包:范围和function被封闭和使用就像任何其他实体。

深入维基百科风格的解释

根据维基百科,封闭是:

在具有一streamfunction的语言中实现词汇范围名称绑定的技术。

这意味着什么? 让我们看看一些定义。

我将通过使用这个例子来解释闭包和其他相关的定义:

 function startAt(x) { return function (y) { return x + y; } } var closure1 = startAt(1); var closure2 = startAt(5); console.log(closure1(3)); // 4 (x == 1, y == 3) console.log(closure2(3)); // 8 (x == 5, y == 3) 

这是另一个真实的例子,并使用在游戏中stream行的脚本语言–Lua。 我需要稍微改变库函数的工作方式,以避免stdin不可用的问题。

 local old_dofile = dofile function dofile( filename ) if filename == nil then error( 'Can not use default of stdin.' ) end old_dofile( filename ) end 

当这段代码完成它的作用域(因为它是本地的)时,old_dofile的值就消失了,但是这个值被包含在一个闭包中,所以新的重定义的dofile函数可以访问它,或者更确切地说, '的upvalue'。

来自Lua.org :

当一个函数被封装在另一个函数中时,它可以完全访问函数中的局部variables; 这个特征被称为词汇范围。 虽然这听起来很明显,但事实并非如此。 词法范围,加上一stream的function,在编程语言中是一个强大的概念,但很less有语言支持这个概念。

如果您来自Java世界,您可以将闭包与类的成员函数进行比较。 看看这个例子

 var f=function(){ var a=7; var g=function(){ return a; } return g; } 

函数g是一个闭包: gclosuresa in。所以g可以和一个成员函数比较, a可以和一个类字段比较,而函数f用一个类来比较。

闭包只要我们在另一个函数中定义了一个函数,内部函数就可以访问外函数中声明的variables。 closures最好用例子来解释。 在清单2-18中,您可以看到内部函数可以从外部范围访问variables(variableInOuterFunction)。 外部函数中的variables已被内部函数closures(或绑定)。 因此,closures这个词。 这个概念本身就很简单,相当直观。

 Listing 2-18: function outerFunction(arg) { var variableInOuterFunction = arg; function bar() { console.log(variableInOuterFunction); // Access a variable from the outer scope } // Call the local function to demonstrate that it has access to arg bar(); } outerFunction('hello closure!'); // logs hello closure! 

来源: http : //index-of.es/Varios/Basarat%20Ali%20Syed%20(auth.)-Beginning%20Node.js-Apress%20(2014).pdf