【问题标题】:Nested Functions, Closures and Scope嵌套函数、闭包和作用域
【发布时间】:2016-08-25 07:10:20
【问题描述】:

我一直在尝试围绕范围,特别是闭包。 我知道有很多关于这个主题的帖子,而且我已经阅读了很多。但是大多数地方都将主题称为高级,并且使用相对难以掌握的术语。我想绝对确定我的基础知识是正确的,这样我就不会冒险进入更复杂的主题,而对函数的真正工作方式有错误的想法。

所以...我选择了一个基本功能,并且真的希望有人告诉我我认为它发生的幕后是否是实际发生的。

这是代码:

function sum(a) {


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

console.log( sum(1)(sum(2)))

(我知道它实际上并没有做总和,我对其进行了调整,试图了解每个步骤中发生了什么。)

所以,我的主要疑问是为什么 A 是 1,而不是 2。我得出的结论是,只要创建了 function(b) 以将 sum(2) 作为参数,就在被返回后立即创建了闭包sum(1)。因此,根据闭包的定义,我假设在创建函数时它还保存了词法环境(其中a = 1)。是这样吗?

我已经制作了一个步骤图。

【问题讨论】:

  • 我了解代码,但不了解您的图表,因此不清楚您所说的大写 As 是什么意思,我不知道如何回答。请注意,sum(2) 返回一个您从未调用过的新函数,因此它的闭包在这里无关紧要。另外,鉴于sum(2) 返回一个新函数,为什么要将它作为参数传递给sum(1) 的返回值?将数字 1 添加到函数引用是没有意义的。 JS 会做(产生一个字符串),但这似乎是一个相当奇怪的测试用例。
  • @nnnnnn 应该是a = 1,每个矩形代表一个词法环境。好的,我认为您刚才所说的关于 sum(2) 的返回从未被调用,有助于理解它。只是为了澄清一下,这里仍然发生了两个闭包,对吗?一个是第一次定义sum(1),另一个是sum(2)
  • @Sean 您收到了多个回复,但没有接受任何回复。究竟是什么导致了您的问题?

标签: javascript nested closures lexical-scope lexical-closures


【解决方案1】:

无论我们使用什么工具将内部函数传输到其词法范围之外,它都会维护一个指向它最初声明位置的范围引用,并且无论我们在哪里执行它,都会执行该闭包。

在下一个函数中,您将看到 greet 将如何使用在 greeting 函数内声明的变量 salute,即使它不再被调用,这称为闭包。

function greeting(name) {
  var salute = "Hello "; // This is a closure

  return function() {
    console.log(salute + name);
  }
}

var greet = greeting("Dave");

greet();    // Hello Dave

您可以在 Kyle Simpson 的丛书You Don't Know JS 中了解更多关于闭包的信息,但对于这个特定主题,请查看You Don't Know JS: Scope & Closures。他使用一种简单而中肯的语言来解释像这样的困难概念。

【讨论】:

  • 与问题中相似但不完全相同的重复代码如何回答问题? OP 的代码还返回一个访问外部函数的变量(参数)的函数。
  • 不一样,这证明你不了解闭包或 JavaScript 是如何工作的,但是如果你阅读了我分享的链接,你就会明白。
  • 我说的是“相似但不完全相同”。您提供了一个与问题中的 sum() 函数没有显着差异的通用闭包示例,并且您还没有回答所提出的问题。
  • @DaveGomez 感谢您抽出宝贵的时间来回答,但我真的在寻找每个步骤的细节,以便对创建的词汇环境和变量范围有一个完整的想象。不过,您的链接看起来不错,再次感谢
【解决方案2】:

这就是发生的事情

1) 如果一个函数返回一个函数并且该返回的函数被立即调用,则称为currying(函数式编程术语)。在此示例中,您混合了 curryingclosure 这两个概念。

2) 首先你的sum(1) 部分被调用。这将返回 function(b) {return a+b} (让我们将其称为 #1st) ,但它将保持活动 a 为 1 仅用于 #1st 的上下文。

3) 由于函数参数本身是一个函数调用,因此该参数部分将被调用。例如sum(1)(sum(2)),这里sum(2) 部分将被调用并返回function(b) {return a+b}(我们将其称为#2nd),它也将保持活动a 作为2 仅用于#2nd 的上下文(闭包)。

4) 现在我们立即以 #2nd 作为参数,使用柯里化语法调用 #1st - #1st(#2nd)

5) 所以我们的 a 是未分配的变量并且具有值 1b 变量具有值 function(b) {return a+b} 。由于我们将这两者连接起来,所以最终输出是1function(b) {return a+b}

注意- a)如果你想要 a+b 的总和而不是那个奇怪的输出,只需将你的最后一行更改为 console.log(sum(1)(2)) 。 b) 如果您注意到闭包 a 在称为 #2nd 的函数中的值为 2,则除了活着之外,永远不会在任何地方使用。

【讨论】:

  • 你对“currying”的定义是不正确的:返回的函数是否被立即调用与currying无关,也不是所有返回函数的函数都是currying的例子。另外,如果没有保留对返回函数的引用,为什么会出现内存泄漏?
  • 1) Ya 更改为`如果保存则可能泄漏`2) 我没有定义柯里化,我只是简单地解释柯里化。
  • @sapy 感谢您抽出宝贵时间。但我并没有真正听从你的解释。当您说“a 是闭包”时,您是什么意思?闭包不是一个函数及其词法环境吗?还有一件事。你说 sum(2) 首先被调用,但是使用这个网站:pythontutor.com/…
  • @sapy 它表明,a 首先被定义为 1。这就是为什么我感到困惑。因为如果关闭首先发生,对我来说很有意义,无论如何 a 总是等于 1。否则,如果以后发生,不应该将a替换为2吗?再次感谢
  • @SeanCobb , 闭包不一定总是一个函数`什么是 javaScript 中的闭包。` 1) 闭包是函数的局部变量 — 在函数返回后保持活动状态,或者 2) 闭包是一个堆栈帧,当函数返回时不会被释放(好像一个'堆栈帧'被malloc'ed而不是在堆栈上!)。
【解决方案3】:

sum 被调用时,它会创建一个作用域。在此范围内,有形参 a 和一个内部匿名函数,它会立即返回。这个匿名函数是一个闭包,因为它捕获了它的封闭函数 (sum) 的范围。因此这个内部函数可以访问a

现在我们来到了一个明显让你感到困惑的地方:内部函数只获取 sum 范围的副本,而不是对原始范围的引用。这意味着如果我们从sum 返回并因此消除其范围,则此副本不受影响(内部函数的a 保持为1)。使用不同参数对sum 的进一步函数调用也不会影响闭包。

结论:闭包的存在时间可以长于其封闭函数。

从技术上讲,suma 存储在堆栈中,而捕获的闭包a 存储在堆中,因此与sum 的生命周期无关。

顺便说一句,你在这里做的事情叫做柯里化。无需使用多个参数调用sum,而是在程序上调用它,每次调用只有一个参数:

sum(1, 2); // multi argument form
sum(1)(2); // curry form

【讨论】:

  • "结论:闭包的存在时间可以长于其封闭函数。" - 那是错的。由于闭包是指函数,所以函数的生命周期至少是闭包的生命周期。相反,您可以说闭包比函数 call. 的寿命更长。
  • 带有“原始范围”的部分也很混乱。
猜你喜欢
  • 2014-07-11
  • 2013-07-07
  • 2023-03-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-27
  • 2017-06-18
相关资源
最近更新 更多