什么是"关闭"?
我问了一个关于咖喱和封闭的问题。
什么是封包?它与咖喱有何关系?
解决方案
回答
我将举一个示例(使用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提供任何参数,因此必须以某种方式记住该计数。它知道根据所谓的词法作用域在哪里找到它,它必须寻找定义它的地方才能找到该值。这个"隐藏"值就是所谓的闭包。
这又是我的例子:
function add (a) { return function (b) { return a + b; } } var add3 = add(3); add3(4); returns 7
我们会看到,当我们使用参数a(即3)调用add时,该值包含在我们定义为add3的返回函数的闭包中。这样,当我们调用add3时,它知道在哪里可以找到一个值来执行加法。
回答
闭包是可以引用另一个函数中的状态的函数。例如,在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
回答
在正常情况下,变量受作用域规则的约束:局部变量仅在定义的函数内起作用。为了方便起见,封闭是暂时打破此规则的一种方法。
def n_times(a_thing) return lambda{|n| a_thing * n} end
在上面的代码中,lambda(| n | a_thing * n}
是闭包,因为a_thing
是由lambda(匿名函数创建者)引用的。
现在,如果将生成的匿名函数放在函数变量中。
foo = n_times(4)
foo将打破常规的作用域规则,并在内部开始使用4.
foo.call(3)
返回12.
回答
凯尔的回答非常好。我认为唯一需要说明的是,在创建lambda函数时,闭包基本上是堆栈的快照。然后,当重新执行该功能时,堆栈将恢复为执行该功能之前的状态。因此,正如Kyle所提到的,当执行lambda函数时,该隐藏值(" count")可用。
回答
为了帮助促进对闭包的理解,研究如何以过程语言实现闭包可能会很有用。该解释将遵循Scheme中对闭包的简化实现。
首先,我必须介绍名称空间的概念。当我们在Scheme解释器中输入命令时,它必须计算表达式中的各种符号并获取它们的值。例子:
(define x 3) (define y 4) (+ x y) returns 7
定义表达式将x的值3存储在x的点中并将y的值4存储在x的点中。然后,当我们调用(+ x y)时,解释器将在名称空间中查找值,并能够执行该操作并返回7.
但是,在Scheme中,有一些表达式可以让我们临时覆盖符号的值。这是一个例子:
(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被本地值临时掩盖了。
程序语言和面向对象的语言具有类似的概念。每当在函数中声明与全局变量同名的变量时,都会得到相同的效果。
我们将如何实施呢?一种简单的方法是使用链表,头包含新值,而尾包含旧名称空间。当我们需要查找符号时,可以从头开始,然后一直到尾部。
现在,让我们暂时跳过一流功能的实现。函数或者多或者少是在调用该函数最终达到返回值时要执行的一组指令。当我们读入一个函数时,我们可以将这些指令存储在幕后,并在调用该函数时运行它们。
(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的操作(+ xy),因为我们在上下文中x等于5时,返回的结果将为9. 这就是所谓的动态作用域。
但是,Scheme,Common Lisp和许多其他语言除了存储操作(+ x y)之外,还具有词法作用域,我们还在该特定点存储名称空间。这样,当我们查找值时,可以看到x在这种情况下实际上是3. 这是一个闭包。
(define x 3) (define (plus-x y) (+ x y)) (let ((x 5)) (plus-x 4)) returns 7
总而言之,我们可以使用链表在函数定义时存储名称空间的状态,从而使我们能够从封闭范围中访问变量,并提供在不影响其余变量的情况下本地屏蔽变量的能力。程序。
回答
这是一个为什么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
很容易,并且它是闭包的:
startPlayback.delay(5000, someTrack); // Keep going, do other things
当我们用5000
ms调用delay
时,第一个代码段运行,并将传入的参数存储在它的闭包中。然后5秒钟后,当setTimeout回调发生时,闭包仍然保留这些变量,因此它可以使用原始参数调用原始函数。
这是一种装饰或者功能装饰。
如果没有闭包,我们将不得不以某种方式保持这些变量在函数外部的状态,从而在函数外部散乱地填充逻辑上属于其内部的代码。使用闭包可以大大提高代码的质量和可读性。