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


当前回答

从Lua.org:

当一个函数被封装在另一个函数中编写时,它可以完全访问封装函数中的局部变量;这个特性称为词法作用域。尽管这听起来很明显,但事实并非如此。词法作用域加上第一类函数是编程语言中一个强大的概念,但很少有语言支持这个概念。

其他回答

闭包为JavaScript提供状态。

状态在编程中仅仅意味着记忆。

例子

var a = 0;

a = a + 1; // => 1
a = a + 1; // => 2
a = a + 1; // => 3

在上面的例子中,state存储在变量“a”中。我们在a后面加几次1。我们之所以能够这样做,是因为我们能够“记住”这个值。状态符“a”将该值保存在内存中。

通常,在编程语言中,您希望跟踪事物、记住信息并在以后访问它。

在其他语言中,这通常是通过使用类来实现的。类,就像变量一样,跟踪它的状态。而该类的实例,依次在它们内部也有状态。状态仅仅意味着您以后可以存储和检索的信息。

例子

class Bread {
  constructor (weight) {
    this.weight = weight;
  }

  render () {
    return `My weight is ${this.weight}!`;
  }
}

我们如何从“渲染”方法中访问“权重”?好吧,感谢国家。Bread类的每个实例都可以通过从“状态”(内存中存储信息的地方)读取它来呈现自己的权重。

JavaScript是一种非常独特的语言,它在历史上没有类(现在有了,但在底层只有函数和变量),所以闭包为JavaScript提供了一种记忆事物并在以后访问它们的方法。

例子

var n = 0;
var count = function () {
  n = n + 1;
  return n;
};

count(); // # 1
count(); // # 2
count(); // # 3

上面的例子实现了用变量“保持状态”的目标。这太棒了!然而,这有一个缺点,即变量(“状态”holder)现在是公开的。我们可以做得更好。我们可以使用闭包。

例子

var countGenerator = function () {
  var n = 0;
  var count = function () {
    n = n + 1;
    return n;
  };

  return count;
};

var count = countGenerator();
count(); // # 1
count(); // # 2
count(); // # 3

这太棒了。

现在我们的count函数可以计数了。它之所以能够这样做,是因为它可以“保持”状态。这种情况下的状态是变量“n”。这个变量现在关闭了。在时间和空间上封闭。因为你永远无法恢复它,改变它,给它赋值或直接与它交互。在空间中,因为它在地理上嵌套在“countGenerator”函数中。

为什么这很神奇?因为不需要涉及任何其他复杂的工具(例如类、方法、实例等),我们就能够 1. 隐藏 2. 从远处控制

我们隐藏了状态,变量“n”,这使它成为一个私有变量! 我们还创建了一个API,可以以预定义的方式控制这个变量。特别地,我们可以像这样调用API“count()”,它从“距离”将1加到“n”。除非通过API,否则任何人都无法访问“n”。

JavaScript的简单性确实令人惊叹。

闭包是其中一个重要原因。

首先,与这里大多数人告诉你的相反,闭包不是一个函数!那么它是什么呢? 它是在函数的“周围上下文”(称为其环境)中定义的一组符号,使其成为CLOSED表达式(即,每个符号都被定义并具有值的表达式,因此可以对其求值)。

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

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

它是一个封闭表达式,因为其中出现的所有符号都在其中定义(它们的含义是明确的),所以您可以对其求值。换句话说,它是独立的。

但如果你有一个这样的函数

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

它是一个开放的表达式,因为其中有尚未定义的符号。也就是y,当我们看这个函数的时候,我们不知道y是什么,它意味着什么,我们不知道它的值,所以我们不能计算这个表达式。也就是说,我们不能调用这个函数,直到我们知道y在其中的含义。这个y叫做自由变量。

这需要一个定义,但这个定义不是函数的一部分——它是在其他地方定义的,在它的“周围上下文”(也称为环境)中。至少这是我们所希望的: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;
  }
}

环境中赋予表达式中自由变量意义的部分是闭包。之所以这么叫,是因为它通过为所有自由变量提供这些缺失的定义,把一个开放表达式变成一个封闭表达式,这样我们就可以求值了。

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

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

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

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

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

这里有更多的理论: https://stackoverflow.com/a/36878651/434562

It's worth to note that in the example above, the wrapper function returns its inner function as a value. The moment we call this function can be remote in time from the moment the function has been defined (or created). In particular, its wrapping function is no longer running, and its parameters which has been on the call stack are no longer there :P This makes a problem, because the inner function needs y to be there when it is called! In other words, it requires the variables from its closure to somehow outlive the wrapper function and be there when needed. Therefore, the inner function has to make a snapshot of these variables which make its closure and store them somewhere safe for later use. (Somewhere outside the call stack.)

And this is why people often confuse the term closure to be that special type of function which can do such snapshots of the external variables they use, or the data structure used to store these variables for later. But I hope you understand now that they are not the closure itself – they're just ways to implement closures in a programming language, or language mechanisms which allows the variables from the function's closure to be there when needed. There's a lot of misconceptions around closures which (unnecessarily) make this subject much more confusing and complicated than it actually is.

从Lua.org:

当一个函数被封装在另一个函数中编写时,它可以完全访问封装函数中的局部变量;这个特性称为词法作用域。尽管这听起来很明显,但事实并非如此。词法作用域加上第一类函数是编程语言中一个强大的概念,但很少有语言支持这个概念。

闭包 只要在另一个函数内部定义了一个函数,内部函数就可以访问声明的变量 在外层函数中。闭包最好用例子来解释。 在清单2-18中,可以看到内部函数可以访问变量variableInOuterFunction 外的范围。外部函数中的变量已被内部函数封闭(或绑定在)。因此才有了这个术语 关闭。这个概念本身很简单,也很直观。

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)。他们% 20 node.js-apress % 20 (2014) . pdf

我将给出一个例子(JavaScript):

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提供任何参数,它必须以某种方式记住计数。它知道根据所谓的词法作用域在哪里找到它——它必须查找定义值的位置才能找到值。这个“隐藏”值就是所谓的闭包。

下面还是我用咖喱的例子:

function add (a) {
  return function (b) {
    return a + b;
  }
}

var add3 = add(3);
    
add3(4); returns 7

您可以看到,当您使用参数a(即3)调用add时,该值包含在我们定义为add3的返回函数的闭包中。这样,当我们调用add3时,它知道在哪里找到a值来执行加法。