我问了一个关于curry和闭包的问题。 什么是闭包?它和咖喱有什么关系?


当前回答

闭包是JavaScript中的一个特性,函数可以访问自己的作用域变量、外部函数变量和全局变量。

闭包即使在外部函数返回之后也可以访问它的外部函数作用域。这意味着闭包可以记住并访问它的外部函数的变量和参数,即使函数已经完成。

内部函数可以访问在自己的作用域、外部函数的作用域和全局作用域中定义的变量。外部函数可以访问在自己的作用域和全局作用域中定义的变量。

闭包的例子:

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不可用的问题。

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”与函数一起存储的副本。

这里有一个现实世界的例子,为什么闭包这么厉害…这是我的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秒。这对于延迟来说是很容易的,它是封闭的:

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

当你调用delay为5000ms时,第一个代码段运行,并将传入的参数存储在它的闭包中。然后5秒后,当setTimeout回调发生时,闭包仍然维护这些变量,因此它可以使用原始参数调用原始函数。 这是一种咖喱,或功能装饰。

如果没有闭包,您将不得不以某种方式在函数外部维护这些变量的状态,从而将函数外部的代码丢弃在逻辑上属于函数内部的代码中。使用闭包可以极大地提高代码的质量和可读性。

为了帮助理解闭包,研究一下如何在过程语言中实现闭包可能会很有用。本解释将遵循Scheme中闭包的简单实现。

首先,我必须介绍名称空间的概念。在Scheme解释器中输入命令时,它必须计算表达式中的各种符号并获得它们的值。例子:

(define x 3)

(define y 4)

(+ x y) returns 7

define表达式将值3存储在x的位置,将值4存储在y的位置。然后当我们调用(+ 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,加-x为它的参数y,加上x的值。最后,在x被一个新的x遮蔽的环境中,我们调用加-x,这个x的值为5。如果我们只存储函数+ 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

总之,我们可以使用链表来存储函数定义时名称空间的状态,从而允许我们从封闭的作用域访问变量,并且能够在不影响程序其余部分的情况下对变量进行局部屏蔽。

在Groovy中有一个简单的例子供您参考:

def outer() {
    def x = 1
    return { -> println(x)} // inner
}
def innerObj = outer()
innerObj() // prints 1

博士tl;

闭包是一个函数,它的作用域赋给(或用作)一个变量。因此,名称闭包:作用域和函数被封闭起来,并像其他实体一样使用。

维基百科式的深度解释

根据维基百科,闭包是:

在具有一流函数的语言中实现词法作用域名称绑定的技术。

这是什么意思?让我们来看看一些定义。

我将通过下面的例子解释闭包和其他相关定义:

函数startAt(x) { 返回函数(y) { 返回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)

一级函数

基本上,这意味着我们可以像使用其他实体一样使用函数。我们可以修改它们,将它们作为参数传递,从函数中返回它们,或者将它们分配给变量。从技术上讲,它们是一等公民,因此得名:一等功能。

在上面的例子中,startAt返回一个(匿名)函数,该函数被分配给closure1和closure2。如你所见,JavaScript对待函数就像对待其他实体一样(一级公民)。

名绑定

名称绑定是关于找出变量(标识符)引用的数据。作用域在这里非常重要,因为它将决定如何解析绑定。

在上面的例子中:

在内部匿名函数的作用域中,y被绑定为3。 在startAt的作用域中,x被绑定为1或5(取决于闭包)。

在匿名函数的作用域中,x没有绑定到任何值,因此需要在upper (startAt)作用域中解析它。

词法作用域

正如维基百科所说,范围:

是计算机程序中绑定有效的区域:其中的名称可用于引用实体。

有两种方法:

词法(静态)作用域:通过搜索包含变量的块或函数来解析变量的定义,如果搜索外部包含块失败,依此类推。 动态作用域:先搜索调用函数,然后搜索调用该函数的函数,依此类推,在调用堆栈中向上移动。

更多的解释,看看这个问题,看看维基百科。

在上面的例子中,我们可以看到JavaScript是词法范围的,因为当解析x时,绑定是在上层(startAt的)范围内搜索的,基于源代码(寻找x的匿名函数是在startAt内部定义的),而不是基于调用堆栈,函数被调用的方式(范围)。

裹(合)起来

In our example, when we call startAt, it will return a (first-class) function that will be assigned to closure1 and closure2 thus a closure is created, because the passed variables 1 and 5 will be saved within startAt's scope, that will be enclosed with the returned anonymous function. When we call this anonymous function via closure1 and closure2 with the same argument (3), the value of y will be found immediately (as that is the parameter of that function), but x is not bound in the scope of the anonymous function, so the resolution continues in the (lexically) upper function scope (that was saved in the closure) where x is found to be bound to either 1 or 5. Now we know everything for the summation so the result can be returned, then printed.

现在您应该了解闭包及其行为,这是JavaScript的基本部分。

局部套用

哦,您还了解了curry的含义:您使用函数(闭包)来传递操作的每个参数,而不是使用带有多个参数的一个函数。