What is a 'Closure'?
我问了一个关于咖喱和闭包的问题。什么是关闭?它与咖喱有什么关系?
可变范围
当您声明一个局部变量时,该变量有一个作用域。通常,局部变量只存在于声明它们的块或函数中。
1 2 3 4 5 | function() { var a = 1; console.log(a); // works } console.log(a); // fails |
如果我试图访问一个局部变量,大多数语言都会在当前范围内查找它,然后在父范围内查找,直到它们到达根范围为止。
1 2 3 4 5 | var a = 1; function() { console.log(a); // works } console.log(a); // works |
当一个块或函数完成时,它的局部变量就不再需要了,通常会耗尽内存。
这就是我们通常所期望的工作方式。
闭包是一个持久的局部变量范围闭包是一个持久的作用域,即使在代码执行从该块中移出之后,它仍然保留着局部变量。支持闭包的语言(如javascript、swift和ruby)将允许您保留对某个范围(包括其父范围)的引用,即使在声明这些变量的块执行完毕之后,只要您在某个地方保留对该块或函数的引用。
作用域对象及其所有局部变量都与函数绑定在一起,并且只要该函数持续存在,它就会一直存在。
这给了我们功能可移植性。我们可以预期,当我们稍后调用函数时,在第一次定义函数时范围内的任何变量仍然在范围内,即使我们在完全不同的上下文中调用函数也是如此。
例如下面是一个非常简单的javascript示例,说明了这一点:
1 2 3 4 5 6 7 8 9 10 | 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(); |
在这里,我定义了一个函数中的一个函数。内部函数可以访问所有外部函数的局部变量,包括
通常,当一个函数退出时,它的所有局部变量都会被吹走。但是,如果我们返回内部函数并将其分配给一个变量
注意变量
你也许可以猜到,当我调用
在没有闭包的语言中,当函数
在javascript中,变量
我将给出一个示例(在javascript中):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | function makeCounter () { var count = 0; return function () { count += 1; return count; } } var x = makeCounter(); x(); returns 1 x(); returns 2 ...etc... |
这个函数makecounter的作用是返回一个函数,我们称之为x,每次调用该函数时,该函数将按1计。因为我们没有给x提供任何参数,它必须记住计数。它知道在什么地方可以根据所谓的词法范围来找到它——它必须找到它定义的地方来找到值。这个"隐藏"值就是所谓的闭包。
下面是我的当前示例:
1 2 3 4 5 6 7 8 9 | function add (a) { return function (b) { return a + b; } } var add3 = add(3); add3(4); returns 7 |
您可以看到,当使用参数a(即3)调用add时,该值包含在我们定义为add3的返回函数的闭包中。这样,当我们调用add3时,它知道在哪里找到执行加法的a值。
凯尔的回答很好。我认为唯一的补充说明是闭包基本上是创建lambda函数时堆栈的快照。然后,当重新执行函数时,在执行函数之前,堆栈将恢复到该状态。因此,正如Kyle提到的,当lambda函数执行时,隐藏值(
首先,与这里大多数人告诉你的相反,闭包并不是一个功能!那是什么?< BR>它是在函数的"环境上下文"(称为其环境)中定义的一组符号,使其成为一个封闭表达式(即,一个表达式,其中定义了每个符号并具有一个值,因此可以对其进行计算)。
例如,当您有一个javascript函数时:
1 2 3 | function closed(x) { return x + 3; } |
它是一个封闭表达式,因为其中出现的所有符号都在其中定义(它们的含义很清楚),所以您可以对其进行评估。换句话说,它是独立的。
但是如果你有这样的功能:
1 2 3 | function open(x) { return x*y + 3; } |
它是一个开放式表达式,因为其中有尚未定义的符号。也就是说,
这个
例如,它可以全局定义:
1 2 3 4 5 | var y = 7; function open(x) { return x*y + 3; } |
或者它可以在包装它的函数中定义:
1 2 3 4 5 6 7 8 9 10 | var global = 2; function wrapper(y) { var w ="unused"; return function(x) { return x*y + 3; } } |
环境的一部分,在一个表达式中给了自由变量它们的含义,那就是闭包。之所以这样叫它,是因为它通过为所有自由变量提供这些缺失的定义,将一个打开的表达式转换为一个关闭的表达式,这样我们就可以计算它了。
在上面的例子中,内部函数(因为我们不需要它而没有给出名称)是一个开放表达式,因为其中的变量
1 2 3 4 5 | { global: 2, w:"unused", y: [whatever has been passed to that wrapper function as its parameter `y`] } |
现在,闭包是这个环境的一部分,它通过提供所有自由变量的定义来关闭内部函数。在我们的例子中,内部函数中唯一的自由变量是
1 2 3 | { y: [whatever has been passed to that wrapper function as its parameter `y`] } |
环境中定义的其他两个符号不是该函数闭包的一部分,因为它不需要运行它们。不需要他们关闭它。
更多关于这背后的理论:https://stackoverflow.com/a/36878651/434562
值得注意的是,在上面的示例中,包装函数以值的形式返回其内部函数。从定义(或创建)函数的那一刻起,我们调用这个函数的那一刻就可以是远程的。特别是,它的包装函数不再运行,它在调用堆栈上的参数也不再存在:p这是一个问题,因为内部函数在调用时需要
这就是为什么人们经常混淆闭包这个词,把它当成可以对所使用的外部变量进行快照的特殊类型的函数,或者把用来存储这些变量供以后使用的数据结构。但我希望您现在理解,它们不是闭包本身——它们只是在编程语言中实现闭包的方法,或者是允许函数闭包中的变量在需要时存在的语言机制。关于闭包有很多误解(不必要的)使这个主题比实际更混乱和复杂。
闭包是可以引用另一个函数中的状态的函数。例如,在Python中,它使用闭包"inner":
1 2 3 4 5 6 7 8 9 | 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 |
为了有助于理解闭包,可以检查如何用过程语言实现闭包。这个解释将遵循方案中闭包的简单实现。
首先,我必须介绍名称空间的概念。在方案解释器中输入命令时,它必须计算表达式中的各种符号并获取它们的值。例子:
1 2 3 4 5 | (define x 3) (define y 4) (+ x y) returns 7 |
define表达式将值3存储在x的spot中,将值4存储在y的spot中。然后,当调用(+x y)时,解释器将查找命名空间中的值,并能够执行该操作并返回7。
但是,在方案中,有些表达式允许您临时重写符号的值。下面是一个例子:
1 2 3 4 5 6 7 8 | (define x 3) (define y 4) (let ((x 5)) (+ x y)) returns 9 x returns 3 |
let关键字的作用是引入一个新的名称空间,其中x值为5。你会注意到它仍然可以看到y是4,使得返回的值为9。您还可以看到,一旦表达式结束,x将恢复为3。从这个意义上讲,x被局部值暂时掩盖了。
程序语言和面向对象语言有类似的概念。每当在与全局变量同名的函数中声明变量时,都会得到相同的效果。
我们将如何实现这一点?一个简单的方法是使用链接列表-头部包含新值,尾部包含旧名称空间。当你需要查找一个符号时,你从头部开始,沿着尾部向下移动。
现在让我们跳到第一类函数的实现。或多或少,函数是一组指令,当函数在返回值中被调用时执行。当我们读入一个函数时,我们可以在后台存储这些指令,并在调用函数时运行它们。
1 2 3 4 5 6 7 | (define x 3) (define (plus-x y) (+ x y)) (let ((x 5)) (plus-x 4)) returns ? |
我们将x定义为3,将plus-x定义为其参数y,再加上x的值。最后,我们在x被新x掩盖的环境中调用plus-x,这个环境值为5。如果我们只存储函数plus-x的操作(+x y),因为我们在x为5的上下文中,返回的结果是9。这就是所谓的动态范围界定。
然而,Scheme、CommonLisp和许多其他语言都有所谓的词汇范围——除了存储操作(+x y)之外,我们还将命名空间存储在那个特定的点上。这样,当我们查找值时,我们可以看到,在这个上下文中,x实际上是3。这是一个终结。
1 2 3 4 5 6 7 | (define x 3) (define (plus-x y) (+ x y)) (let ((x 5)) (plus-x 4)) returns 7 |
总之,我们可以使用一个链接列表来存储函数定义时命名空间的状态,允许我们从封闭范围访问变量,并提供我们在不影响程序其余部分的情况下本地屏蔽变量的能力。
这里有一个现实世界的例子说明为什么闭包会踢屁股…这完全是我的javascript代码。让我举例说明。
1 2 3 4 5 6 7 8 | 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); }; |
以下是您将如何使用它:
1 2 3 4 | var startPlayback = function(track) { Player.play(track); }; startPlayback(someTrack); |
现在假设您希望播放延迟,例如在运行此代码段后5秒。好吧,使用EDOCX1[0]很容易,而且它是关闭的:
1 2 | startPlayback.delay(5000, someTrack); // Keep going, do other things |
当使用
如果没有闭包,您将不得不在函数外部以某种方式维护这些变量的状态,从而将函数外部的代码与逻辑上属于函数内部的内容一起丢弃。使用闭包可以大大提高代码的质量和可读性。
DR
闭包是一个函数,它的作用域被分配(或用作)一个变量。因此,名称闭包:作用域和函数与任何其他实体一样被封闭和使用。
深入的维基百科风格解释根据维基百科的说法,结束语是:
Techniques for implementing lexically scoped name binding in languages with first-class functions.
那是什么意思?我们来看看一些定义。
我将使用以下示例解释闭包和其他相关定义:
1 2 3 4 5 6 7 8 9 10 11 | 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) |
基本上,这意味着我们可以像其他实体一样使用函数。我们可以修改它们,将它们作为参数传递,从函数返回它们,或者为变量分配它们。从技术上讲,他们是一流的公民,因此得名:一流的职能。
在上面的示例中,
名称绑定是为了找出变量(标识符)引用的数据。作用域在这里非常重要,因为它将决定如何解析绑定。
在上面的示例中:
- 在内部匿名函数的作用域内,
y 与3 绑定。 - 在
startAt 的范围内,x 与1 或5 绑定(视关闭情况而定)。
在匿名函数的作用域内,
正如维基百科所说,范围:
Is the region of a computer program where the binding is valid: where the name can be used to refer to the entity.
有两种方法:
- 词法(静态)作用域:通过搜索变量的包含块或函数来解析变量的定义,如果搜索失败,则搜索外部包含块,依此类推。
- 动态作用域:先搜索调用函数,然后搜索调用该调用函数的函数,依此类推,继续调用堆栈。
有关更多解释,请查看这个问题并查看维基百科。
在上面的例子中,我们可以看到javascript在词法上的作用域,因为当解析
在我们的示例中,当我们调用
现在,您应该了解闭包以及它们的行为,这是JavaScript的基本部分。
咖喱哦,您还了解了curring的含义:使用函数(闭包)传递操作的每个参数,而不是使用一个带有多个参数的函数。
不包含自由变量的函数称为纯函数。包含一个或多个自由变量的函数称为闭包。
1 2 3 4 5 6 7 8 9 10 11 | 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如果没有自由变量的函数是纯闭包,则为不纯闭包。
在正常情况下,变量受范围规则约束:局部变量仅在定义的函数内工作。关闭是为了方便而暂时打破这一规则的一种方式。
1 2 3 | def n_times(a_thing) return lambda{|n| a_thing * n} end |
在上面的代码中,
现在,如果将生成的匿名函数放入一个函数变量中。
1 | foo = n_times(4) |
foo将打破正常范围规则,并开始在内部使用4。
1 | foo.call(3) |
返回12。
简而言之,函数指针只是指向程序代码库中某个位置的指针(如程序计数器)。而闭包=函数指针+堆栈帧。
.
闭包是JavaScript中的一个特性,函数可以访问自己的作用域变量、访问外部函数变量和访问全局变量。
即使在外部函数返回之后,闭包也可以访问其外部函数范围。这意味着闭包可以记住并访问其外部函数的变量和参数,即使在函数完成之后也是如此。
内部函数可以访问在其自身范围、外部函数范围和全局范围中定义的变量。外部函数可以访问其自身作用域和全局作用域中定义的变量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | ****************** Example of Closure ****************** var globalValue = 5; function functOuter() { var outerFunctionValue = 10; //Inner function has access to the outer function value //and the global variables function functInner() { var innerFunctionValue = 5; alert(globalValue+outerFunctionValue + innerFunctionValue); } functInner(); } functOuter(); |
输出为20,其内部函数自身变量、外部函数变量和全局变量值之和。
这是另一个真实的例子,使用了一种在游戏中很流行的脚本语言——Lua。我需要稍微改变库函数的工作方式,以避免stdin不可用的问题。
1 2 3 4 5 6 7 8 9 | local old_dofile = dofile function dofile( filename ) if filename == nil then error( 'Can not use default of stdin.' ) end old_dofile( filename ) end |
当这段代码完成它的作用域(因为它是本地的)时,旧的"dofile"的值将消失,但是该值已包含在一个闭包中,因此新的重新定义的dofile函数可以访问它,或者更确切地说,与函数一起存储的副本作为"upvalue"。
当前:它允许您通过只传递函数参数的一个子集来部分地评估函数。考虑一下:
1 2 3 4 5 6 7 8 9 | function multiply (x, y) { return x * y; } const double = multiply.bind(null, 2); const eight = double(4); eight == 8; |
闭包:闭包只不过是访问函数范围之外的变量。必须记住,函数或嵌套函数内的函数不是闭包。当需要访问函数范围之外的变量时,总是使用闭包。
1 2 3 4 5 6 7 8 9 10 | function apple(x){ function google(y,z) { console.log(x*y); } google(7,2); } apple(3); // the answer here will be 21 |
如果您来自Java世界,可以将闭包与类的成员函数进行比较。看看这个例子
1 2 3 4 5 6 7 | var f=function(){ var a=7; var g=function(){ return a; } return g; } |
函数
闭包每当我们在另一个函数中定义了一个函数时,内部函数就可以访问声明的变量。在外部功能中。闭包最好用示例来解释。在清单2-18中,可以看到内部函数从外部范围。外部函数中的变量已由内部函数关闭(或绑定)。因此这个词关闭。这个概念本身很简单,也相当直观。
1 2 3 4 5 6 7 8 9 10 11 | 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/varos/basarat%20ali%20syed%20(auth.)-beginning%20node.js apress%20(2014).pdf
请看下面的代码,以更深入地了解闭包:
1 2 3 4 5 | for(var i=0; i< 5; i++){ setTimeout(function(){ console.log(i); }, 1000); } |
这里将输出什么?由于关闭,
那么它将如何解决呢?答案如下:
1 2 3 4 5 6 7 | for(var i=0; i< 5; i++){ (function(j){ //using IIFE setTimeout(function(){ console.log(j); },1000); })(i); } |
让我简单解释一下,当一个函数创建时,直到它在第1个代码中调用了5次循环,但没有立即调用,所以当它调用时,也就是在1秒之后,这是异步的,所以在这个for循环完成之前,将值5存储在var i中,最后执行
这里介绍如何使用IIFE解决问题,即立即调用函数表达式
1 2 3 4 5 | (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 |
有关更多信息,请理解执行上下文以了解闭包。
还有一个解决方案可以使用let(ES6功能)解决这个问题,但是在引擎盖下面,上面的功能可以工作。
1
2
3
4
5
6
7for(let i=0; i< 5; i++){
setTimeout(function(){
console.log(i);
},1000);
}
Output: 0,1,2,3,4
=>更多说明:
在内存中,当for循环执行以下图片制作:
循环1)
1 2 3 | setTimeout(function(){ console.log(i); },1000); |
循环2)
1 2 3 | setTimeout(function(){ console.log(i); },1000); |
循环3)
1 2 3 | setTimeout(function(){ console.log(i); },1000); |
循环4)
1 2 3 | setTimeout(function(){ console.log(i); },1000); |
循环5)
1 2 3 | setTimeout(function(){ console.log(i); },1000); |
这里我不执行,然后在完成循环后,var i将值5存储在内存中,但是它的作用域在它的子函数中总是可见的,所以当函数在
所以要解决这个问题,请使用上面解释的IIFE。
来自LuA.Org:
When a function is written enclosed in another function, it has full access to local variables from the enclosing function; this feature is called lexical scoping. Although that may sound obvious, it is not. Lexical scoping, plus first-class functions, is a powerful concept in a programming language, but few languages support that concept.